mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-11 01:35:22 +01:00
Form System Flexibility improvements (#4774)
* Introduce very flexible form input system * Refactorings after rebase * Test fix * Update BTCPayServer/Forms/FormDataService.cs --------- Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
This commit is contained in:
parent
11f05285a1
commit
60d6e98c67
13 changed files with 92 additions and 48 deletions
|
@ -135,25 +135,5 @@ public class Form
|
|||
}
|
||||
}
|
||||
|
||||
public JObject GetValues()
|
||||
{
|
||||
var r = new JObject();
|
||||
foreach (var f in GetAllFields())
|
||||
{
|
||||
var node = r;
|
||||
for (int i = 0; i < f.Path.Count - 1; i++)
|
||||
{
|
||||
var p = f.Path[i];
|
||||
var child = node[p] as JObject;
|
||||
if (child is null)
|
||||
{
|
||||
child = new JObject();
|
||||
node[p] = child;
|
||||
}
|
||||
node = child;
|
||||
}
|
||||
node[f.Field.Name] = f.Field.Value;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Forms;
|
||||
|
@ -42,9 +40,10 @@ public class FormTests : UnitTestBase
|
|||
}
|
||||
}
|
||||
};
|
||||
var service = new FormDataService(null, null);
|
||||
var providers = new FormComponentProviders(new List<IFormComponentProvider>());
|
||||
var service = new FormDataService(null, providers);
|
||||
Assert.False(service.IsFormSchemaValid(form.ToString(), out _, out _));
|
||||
form = new Form()
|
||||
form = new Form
|
||||
{
|
||||
Fields = new List<Field>
|
||||
{
|
||||
|
@ -161,12 +160,12 @@ public class FormTests : UnitTestBase
|
|||
}
|
||||
}
|
||||
|
||||
var obj = form.GetValues();
|
||||
var obj = service.GetValues(form);
|
||||
Assert.Equal("original", obj["invoice"]["test"].Value<string>());
|
||||
Assert.Equal("updated", obj["invoice_item3"].Value<string>());
|
||||
Clear(form);
|
||||
form.SetValues(obj);
|
||||
obj = form.GetValues();
|
||||
obj = service.GetValues(form);
|
||||
Assert.Equal("original", obj["invoice"]["test"].Value<string>());
|
||||
Assert.Equal("updated", obj["invoice_item3"].Value<string>());
|
||||
|
||||
|
@ -184,10 +183,10 @@ public class FormTests : UnitTestBase
|
|||
}
|
||||
};
|
||||
form.SetValues(obj);
|
||||
obj = form.GetValues();
|
||||
obj = service.GetValues(form);
|
||||
Assert.Null(obj["test"].Value<string>());
|
||||
form.SetValues(new JObject{ ["test"] = "hello" });
|
||||
obj = form.GetValues();
|
||||
obj = service.GetValues(form);
|
||||
Assert.Equal("hello", obj["test"].Value<string>());
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ using BTCPayServer.Client;
|
|||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Forms;
|
||||
using BTCPayServer.Models.CustodianAccountViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
|
@ -38,6 +39,7 @@ namespace BTCPayServer.Controllers
|
|||
private readonly BTCPayServerClient _btcPayServerClient;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly FormDataService _formDataService;
|
||||
|
||||
public UICustodianAccountsController(
|
||||
DisplayFormatter displayFormatter,
|
||||
|
@ -46,7 +48,8 @@ namespace BTCPayServer.Controllers
|
|||
IEnumerable<ICustodian> custodianRegistry,
|
||||
BTCPayServerClient btcPayServerClient,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
LinkGenerator linkGenerator
|
||||
LinkGenerator linkGenerator,
|
||||
FormDataService formDataService
|
||||
)
|
||||
{
|
||||
_displayFormatter = displayFormatter;
|
||||
|
@ -55,6 +58,7 @@ namespace BTCPayServer.Controllers
|
|||
_btcPayServerClient = btcPayServerClient;
|
||||
_networkProvider = networkProvider;
|
||||
_linkGenerator = linkGenerator;
|
||||
_formDataService = formDataService;
|
||||
}
|
||||
|
||||
public string CreatedCustodianAccountId { get; set; }
|
||||
|
@ -247,7 +251,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
if (configForm.IsValid())
|
||||
{
|
||||
var newData = configForm.GetValues();
|
||||
var newData = _formDataService.GetValues(configForm);
|
||||
custodianAccount.SetBlob(newData);
|
||||
custodianAccount = await _custodianAccountRepository.CreateOrUpdate(custodianAccount);
|
||||
return RedirectToAction(nameof(ViewCustodianAccount),
|
||||
|
@ -301,7 +305,7 @@ namespace BTCPayServer.Controllers
|
|||
configForm.ApplyValuesFromForm(Request.Form);
|
||||
if (configForm.IsValid())
|
||||
{
|
||||
var configData = configForm.GetValues();
|
||||
var configData = _formDataService.GetValues(configForm);
|
||||
custodianAccountData.SetBlob(configData);
|
||||
custodianAccountData = await _custodianAccountRepository.CreateOrUpdate(custodianAccountData);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Custodian account successfully created";
|
||||
|
|
|
@ -240,7 +240,7 @@ namespace BTCPayServer.Controllers
|
|||
form.ApplyValuesFromForm(Request.Form);
|
||||
if (FormDataService.Validate(form, ModelState))
|
||||
{
|
||||
prBlob.FormResponse = form.GetValues();
|
||||
prBlob.FormResponse = FormDataService.GetValues(form);
|
||||
result.SetBlob(prBlob);
|
||||
await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(result);
|
||||
return RedirectToAction("PayPaymentRequest", new {payReqId});
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -15,6 +14,7 @@ public static class FormDataExtensions
|
|||
serviceCollection.AddSingleton<IFormComponentProvider, HtmlInputFormProvider>();
|
||||
serviceCollection.AddSingleton<IFormComponentProvider, HtmlFieldsetFormProvider>();
|
||||
serviceCollection.AddSingleton<IFormComponentProvider, HtmlSelectFormProvider>();
|
||||
serviceCollection.AddSingleton<IFormComponentProvider, FieldValueMirror>();
|
||||
}
|
||||
|
||||
public static JObject Deserialize(this FormData form)
|
||||
|
|
|
@ -11,6 +11,7 @@ using BTCPayServer.Data;
|
|||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Forms;
|
||||
|
||||
|
@ -150,14 +151,50 @@ public class FormDataService
|
|||
|
||||
public CreateInvoiceRequest GenerateInvoiceParametersFromForm(Form form)
|
||||
{
|
||||
var amt = form.GetFieldByFullName($"{InvoiceParameterPrefix}amount")?.Value;
|
||||
var amt = GetValue(form, $"{InvoiceParameterPrefix}amount");
|
||||
return new CreateInvoiceRequest
|
||||
{
|
||||
Currency = form.GetFieldByFullName($"{InvoiceParameterPrefix}currency")?.Value,
|
||||
Currency = GetValue(form, $"{InvoiceParameterPrefix}currency"),
|
||||
Amount = string.IsNullOrEmpty(amt) ? null : decimal.Parse(amt, CultureInfo.InvariantCulture),
|
||||
|
||||
Metadata = form.GetValues(),
|
||||
|
||||
Metadata = GetValues(form),
|
||||
};
|
||||
}
|
||||
|
||||
public string? GetValue(Form form, string field)
|
||||
{
|
||||
return GetValue(form, form.GetFieldByFullName(field));
|
||||
}
|
||||
|
||||
public string? GetValue(Form form, Field? field)
|
||||
{
|
||||
if (field is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return _formProviders.TypeToComponentProvider.TryGetValue(field.Type, out var formComponentProvider) ? formComponentProvider.GetValue(form, field) : field.Value;
|
||||
}
|
||||
|
||||
public JObject GetValues(Form form)
|
||||
{
|
||||
var r = new JObject();
|
||||
|
||||
foreach (var f in form.GetAllFields())
|
||||
{
|
||||
var node = r;
|
||||
for (int i = 0; i < f.Path.Count - 1; i++)
|
||||
{
|
||||
var p = f.Path[i];
|
||||
var child = node[p] as JObject;
|
||||
if (child is null)
|
||||
{
|
||||
child = new JObject();
|
||||
node[p] = child;
|
||||
}
|
||||
node = child;
|
||||
}
|
||||
|
||||
node[f.Field.Name] = GetValue(form, f.FullName);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
|
||||
namespace BTCPayServer.Forms;
|
||||
|
@ -13,8 +12,9 @@ public class HtmlFieldsetFormProvider : IFormComponentProvider
|
|||
typeToComponentProvider.Add("fieldset", this);
|
||||
}
|
||||
|
||||
public void Validate(Field field)
|
||||
public string GetValue(Form form, Field field)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Validate(Form form, Field field)
|
||||
|
|
|
@ -1,11 +1,31 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Validation;
|
||||
|
||||
namespace BTCPayServer.Forms;
|
||||
|
||||
public class FieldValueMirror : IFormComponentProvider
|
||||
{
|
||||
public string View { get; } = null;
|
||||
public void Validate(Form form, Field field)
|
||||
{
|
||||
if (form.GetFieldByFullName(field.Value) is null)
|
||||
{
|
||||
field.ValidationErrors = new List<string>() {$"{field.Name} requires {field.Value} to be present"};
|
||||
}
|
||||
}
|
||||
|
||||
public void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider)
|
||||
{
|
||||
typeToComponentProvider.Add("mirror", this);
|
||||
}
|
||||
|
||||
public string GetValue(Form form, Field field)
|
||||
{
|
||||
return form.GetFieldByFullName(field.Value)?.Value;
|
||||
}
|
||||
}
|
||||
public class HtmlInputFormProvider : FormComponentProviderBase
|
||||
{
|
||||
public override void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider)
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace BTCPayServer.Forms;
|
||||
|
|
|
@ -9,12 +9,18 @@ public interface IFormComponentProvider
|
|||
string View { get; }
|
||||
void Validate(Form form, Field field);
|
||||
void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider);
|
||||
string GetValue(Form form, Field field);
|
||||
}
|
||||
|
||||
public abstract class FormComponentProviderBase : IFormComponentProvider
|
||||
{
|
||||
public abstract string View { get; }
|
||||
public abstract void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider);
|
||||
public virtual string GetValue(Form form, Field field)
|
||||
{
|
||||
return field.Value;
|
||||
}
|
||||
|
||||
public abstract void Validate(Form form, Field field);
|
||||
|
||||
public void ValidateField<T>(Field field) where T : ValidationAttribute, new()
|
||||
|
|
|
@ -267,7 +267,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
return RedirectToAction(nameof(ViewPointOfSale), new { appId, viewType });
|
||||
}
|
||||
|
||||
formResponseJObject = form.GetValues();
|
||||
formResponseJObject = FormDataService.GetValues(form);
|
||||
break;
|
||||
}
|
||||
try
|
||||
|
@ -406,7 +406,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
var controller = nameof(UIPointOfSaleController).TrimEnd("Controller", StringComparison.InvariantCulture);
|
||||
var redirectUrl =
|
||||
Request.GetAbsoluteUri(Url.Action(nameof(ViewPointOfSale), controller, new {appId, viewType}));
|
||||
formParameters.Add("formResponse", form.GetValues().ToString());
|
||||
formParameters.Add("formResponse", FormDataService.GetValues(form).ToString());
|
||||
return View("PostRedirect", new PostRedirectViewModel
|
||||
{
|
||||
FormUrl = redirectUrl,
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<legend class="h3 mt-4 mb-3">@Model.Label</legend>
|
||||
@foreach (var field in Model.Fields)
|
||||
{
|
||||
if (FormComponentProviders.TypeToComponentProvider.TryGetValue(field.Type, out var partial))
|
||||
if (FormComponentProviders.TypeToComponentProvider.TryGetValue(field.Type, out var partial) && !string.IsNullOrEmpty(partial.View))
|
||||
{
|
||||
field.Name = $"{(string.IsNullOrEmpty(Model.Name)? string.Empty: $"{Model.Name}_")}{field.Name}";
|
||||
<partial name="@partial.View" for="@field"></partial>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
@foreach (var field in Model.Fields)
|
||||
{
|
||||
if (FormComponentProviders.TypeToComponentProvider.TryGetValue(field.Type, out var partial))
|
||||
if (FormComponentProviders.TypeToComponentProvider.TryGetValue(field.Type, out var partial) && !string.IsNullOrEmpty(partial.View))
|
||||
{
|
||||
<partial name="@partial.View" for="@field" />
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue