mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-12 10:30:47 +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.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BTCPayServer.Abstractions.Form;
|
using BTCPayServer.Abstractions.Form;
|
||||||
using BTCPayServer.Forms;
|
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 _));
|
Assert.False(service.IsFormSchemaValid(form.ToString(), out _, out _));
|
||||||
form = new Form()
|
form = new Form
|
||||||
{
|
{
|
||||||
Fields = new List<Field>
|
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("original", obj["invoice"]["test"].Value<string>());
|
||||||
Assert.Equal("updated", obj["invoice_item3"].Value<string>());
|
Assert.Equal("updated", obj["invoice_item3"].Value<string>());
|
||||||
Clear(form);
|
Clear(form);
|
||||||
form.SetValues(obj);
|
form.SetValues(obj);
|
||||||
obj = form.GetValues();
|
obj = service.GetValues(form);
|
||||||
Assert.Equal("original", obj["invoice"]["test"].Value<string>());
|
Assert.Equal("original", obj["invoice"]["test"].Value<string>());
|
||||||
Assert.Equal("updated", obj["invoice_item3"].Value<string>());
|
Assert.Equal("updated", obj["invoice_item3"].Value<string>());
|
||||||
|
|
||||||
|
@ -184,10 +183,10 @@ public class FormTests : UnitTestBase
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
form.SetValues(obj);
|
form.SetValues(obj);
|
||||||
obj = form.GetValues();
|
obj = service.GetValues(form);
|
||||||
Assert.Null(obj["test"].Value<string>());
|
Assert.Null(obj["test"].Value<string>());
|
||||||
form.SetValues(new JObject{ ["test"] = "hello" });
|
form.SetValues(new JObject{ ["test"] = "hello" });
|
||||||
obj = form.GetValues();
|
obj = service.GetValues(form);
|
||||||
Assert.Equal("hello", obj["test"].Value<string>());
|
Assert.Equal("hello", obj["test"].Value<string>());
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ using BTCPayServer.Client;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Filters;
|
using BTCPayServer.Filters;
|
||||||
|
using BTCPayServer.Forms;
|
||||||
using BTCPayServer.Models.CustodianAccountViewModels;
|
using BTCPayServer.Models.CustodianAccountViewModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
@ -38,6 +39,7 @@ namespace BTCPayServer.Controllers
|
||||||
private readonly BTCPayServerClient _btcPayServerClient;
|
private readonly BTCPayServerClient _btcPayServerClient;
|
||||||
private readonly BTCPayNetworkProvider _networkProvider;
|
private readonly BTCPayNetworkProvider _networkProvider;
|
||||||
private readonly LinkGenerator _linkGenerator;
|
private readonly LinkGenerator _linkGenerator;
|
||||||
|
private readonly FormDataService _formDataService;
|
||||||
|
|
||||||
public UICustodianAccountsController(
|
public UICustodianAccountsController(
|
||||||
DisplayFormatter displayFormatter,
|
DisplayFormatter displayFormatter,
|
||||||
|
@ -46,7 +48,8 @@ namespace BTCPayServer.Controllers
|
||||||
IEnumerable<ICustodian> custodianRegistry,
|
IEnumerable<ICustodian> custodianRegistry,
|
||||||
BTCPayServerClient btcPayServerClient,
|
BTCPayServerClient btcPayServerClient,
|
||||||
BTCPayNetworkProvider networkProvider,
|
BTCPayNetworkProvider networkProvider,
|
||||||
LinkGenerator linkGenerator
|
LinkGenerator linkGenerator,
|
||||||
|
FormDataService formDataService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_displayFormatter = displayFormatter;
|
_displayFormatter = displayFormatter;
|
||||||
|
@ -55,6 +58,7 @@ namespace BTCPayServer.Controllers
|
||||||
_btcPayServerClient = btcPayServerClient;
|
_btcPayServerClient = btcPayServerClient;
|
||||||
_networkProvider = networkProvider;
|
_networkProvider = networkProvider;
|
||||||
_linkGenerator = linkGenerator;
|
_linkGenerator = linkGenerator;
|
||||||
|
_formDataService = formDataService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CreatedCustodianAccountId { get; set; }
|
public string CreatedCustodianAccountId { get; set; }
|
||||||
|
@ -247,7 +251,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
if (configForm.IsValid())
|
if (configForm.IsValid())
|
||||||
{
|
{
|
||||||
var newData = configForm.GetValues();
|
var newData = _formDataService.GetValues(configForm);
|
||||||
custodianAccount.SetBlob(newData);
|
custodianAccount.SetBlob(newData);
|
||||||
custodianAccount = await _custodianAccountRepository.CreateOrUpdate(custodianAccount);
|
custodianAccount = await _custodianAccountRepository.CreateOrUpdate(custodianAccount);
|
||||||
return RedirectToAction(nameof(ViewCustodianAccount),
|
return RedirectToAction(nameof(ViewCustodianAccount),
|
||||||
|
@ -301,7 +305,7 @@ namespace BTCPayServer.Controllers
|
||||||
configForm.ApplyValuesFromForm(Request.Form);
|
configForm.ApplyValuesFromForm(Request.Form);
|
||||||
if (configForm.IsValid())
|
if (configForm.IsValid())
|
||||||
{
|
{
|
||||||
var configData = configForm.GetValues();
|
var configData = _formDataService.GetValues(configForm);
|
||||||
custodianAccountData.SetBlob(configData);
|
custodianAccountData.SetBlob(configData);
|
||||||
custodianAccountData = await _custodianAccountRepository.CreateOrUpdate(custodianAccountData);
|
custodianAccountData = await _custodianAccountRepository.CreateOrUpdate(custodianAccountData);
|
||||||
TempData[WellKnownTempData.SuccessMessage] = "Custodian account successfully created";
|
TempData[WellKnownTempData.SuccessMessage] = "Custodian account successfully created";
|
||||||
|
|
|
@ -240,7 +240,7 @@ namespace BTCPayServer.Controllers
|
||||||
form.ApplyValuesFromForm(Request.Form);
|
form.ApplyValuesFromForm(Request.Form);
|
||||||
if (FormDataService.Validate(form, ModelState))
|
if (FormDataService.Validate(form, ModelState))
|
||||||
{
|
{
|
||||||
prBlob.FormResponse = form.GetValues();
|
prBlob.FormResponse = FormDataService.GetValues(form);
|
||||||
result.SetBlob(prBlob);
|
result.SetBlob(prBlob);
|
||||||
await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(result);
|
await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(result);
|
||||||
return RedirectToAction("PayPaymentRequest", new {payReqId});
|
return RedirectToAction("PayPaymentRequest", new {payReqId});
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
using BTCPayServer.Abstractions.Form;
|
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
@ -15,6 +14,7 @@ public static class FormDataExtensions
|
||||||
serviceCollection.AddSingleton<IFormComponentProvider, HtmlInputFormProvider>();
|
serviceCollection.AddSingleton<IFormComponentProvider, HtmlInputFormProvider>();
|
||||||
serviceCollection.AddSingleton<IFormComponentProvider, HtmlFieldsetFormProvider>();
|
serviceCollection.AddSingleton<IFormComponentProvider, HtmlFieldsetFormProvider>();
|
||||||
serviceCollection.AddSingleton<IFormComponentProvider, HtmlSelectFormProvider>();
|
serviceCollection.AddSingleton<IFormComponentProvider, HtmlSelectFormProvider>();
|
||||||
|
serviceCollection.AddSingleton<IFormComponentProvider, FieldValueMirror>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JObject Deserialize(this FormData form)
|
public static JObject Deserialize(this FormData form)
|
||||||
|
|
|
@ -11,6 +11,7 @@ using BTCPayServer.Data;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Forms;
|
namespace BTCPayServer.Forms;
|
||||||
|
|
||||||
|
@ -150,14 +151,50 @@ public class FormDataService
|
||||||
|
|
||||||
public CreateInvoiceRequest GenerateInvoiceParametersFromForm(Form form)
|
public CreateInvoiceRequest GenerateInvoiceParametersFromForm(Form form)
|
||||||
{
|
{
|
||||||
var amt = form.GetFieldByFullName($"{InvoiceParameterPrefix}amount")?.Value;
|
var amt = GetValue(form, $"{InvoiceParameterPrefix}amount");
|
||||||
return new CreateInvoiceRequest
|
return new CreateInvoiceRequest
|
||||||
{
|
{
|
||||||
Currency = form.GetFieldByFullName($"{InvoiceParameterPrefix}currency")?.Value,
|
Currency = GetValue(form, $"{InvoiceParameterPrefix}currency"),
|
||||||
Amount = string.IsNullOrEmpty(amt) ? null : decimal.Parse(amt, CultureInfo.InvariantCulture),
|
Amount = string.IsNullOrEmpty(amt) ? null : decimal.Parse(amt, CultureInfo.InvariantCulture),
|
||||||
|
Metadata = GetValues(form),
|
||||||
Metadata = form.GetValues(),
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using BTCPayServer.Abstractions.Form;
|
using BTCPayServer.Abstractions.Form;
|
||||||
|
|
||||||
namespace BTCPayServer.Forms;
|
namespace BTCPayServer.Forms;
|
||||||
|
@ -13,8 +12,9 @@ public class HtmlFieldsetFormProvider : IFormComponentProvider
|
||||||
typeToComponentProvider.Add("fieldset", this);
|
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)
|
public void Validate(Form form, Field field)
|
||||||
|
|
|
@ -1,11 +1,31 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
|
||||||
using BTCPayServer.Abstractions.Form;
|
using BTCPayServer.Abstractions.Form;
|
||||||
using BTCPayServer.Validation;
|
using BTCPayServer.Validation;
|
||||||
|
|
||||||
namespace BTCPayServer.Forms;
|
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 class HtmlInputFormProvider : FormComponentProviderBase
|
||||||
{
|
{
|
||||||
public override void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider)
|
public override void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
|
||||||
using BTCPayServer.Abstractions.Form;
|
using BTCPayServer.Abstractions.Form;
|
||||||
using BTCPayServer.Validation;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
|
||||||
namespace BTCPayServer.Forms;
|
namespace BTCPayServer.Forms;
|
||||||
|
|
|
@ -9,12 +9,18 @@ public interface IFormComponentProvider
|
||||||
string View { get; }
|
string View { get; }
|
||||||
void Validate(Form form, Field field);
|
void Validate(Form form, Field field);
|
||||||
void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider);
|
void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider);
|
||||||
|
string GetValue(Form form, Field field);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class FormComponentProviderBase : IFormComponentProvider
|
public abstract class FormComponentProviderBase : IFormComponentProvider
|
||||||
{
|
{
|
||||||
public abstract string View { get; }
|
public abstract string View { get; }
|
||||||
public abstract void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider);
|
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 abstract void Validate(Form form, Field field);
|
||||||
|
|
||||||
public void ValidateField<T>(Field field) where T : ValidationAttribute, new()
|
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 });
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId, viewType });
|
||||||
}
|
}
|
||||||
|
|
||||||
formResponseJObject = form.GetValues();
|
formResponseJObject = FormDataService.GetValues(form);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
|
@ -406,7 +406,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||||
var controller = nameof(UIPointOfSaleController).TrimEnd("Controller", StringComparison.InvariantCulture);
|
var controller = nameof(UIPointOfSaleController).TrimEnd("Controller", StringComparison.InvariantCulture);
|
||||||
var redirectUrl =
|
var redirectUrl =
|
||||||
Request.GetAbsoluteUri(Url.Action(nameof(ViewPointOfSale), controller, new {appId, viewType}));
|
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
|
return View("PostRedirect", new PostRedirectViewModel
|
||||||
{
|
{
|
||||||
FormUrl = redirectUrl,
|
FormUrl = redirectUrl,
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<legend class="h3 mt-4 mb-3">@Model.Label</legend>
|
<legend class="h3 mt-4 mb-3">@Model.Label</legend>
|
||||||
@foreach (var field in Model.Fields)
|
@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}";
|
field.Name = $"{(string.IsNullOrEmpty(Model.Name)? string.Empty: $"{Model.Name}_")}{field.Name}";
|
||||||
<partial name="@partial.View" for="@field"></partial>
|
<partial name="@partial.View" for="@field"></partial>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
@foreach (var field in Model.Fields)
|
@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" />
|
<partial name="@partial.View" for="@field" />
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue