Skip to content

Commit

Permalink
Form System Flexibility improvements (btcpayserver#4774)
Browse files Browse the repository at this point in the history
* Introduce very flexible form input system

* Refactorings after rebase

* Test fix

* Update BTCPayServer/Forms/FormDataService.cs

---------

Co-authored-by: Dennis Reimann <[email protected]>
  • Loading branch information
Kukks and dennisreimann authored Apr 4, 2023
1 parent 11f0528 commit 60d6e98
Show file tree
Hide file tree
Showing 13 changed files with 92 additions and 48 deletions.
22 changes: 1 addition & 21 deletions BTCPayServer.Abstractions/Form/Form.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,25 +135,5 @@ private void SetValues(Dictionary<string, Field> fields, List<string> path, JObj
}
}

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;
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using BTCPayServer.Abstractions.Form;
using BTCPayServer.Forms;
Expand Down Expand Up @@ -42,9 +40,10 @@ public void CanParseForm()
}
}
};
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>
{
Expand Down Expand Up @@ -161,12 +160,12 @@ public void CanParseForm()
}
}

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>());

Expand All @@ -184,10 +183,10 @@ public void CanParseForm()
}
};
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>());
}

Expand Down
10 changes: 7 additions & 3 deletions BTCPayServer/Controllers/UICustodianAccountsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Filters;
using BTCPayServer.Forms;
using BTCPayServer.Models.CustodianAccountViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Services;
Expand Down Expand Up @@ -38,6 +39,7 @@ public class UICustodianAccountsController : Controller
private readonly BTCPayServerClient _btcPayServerClient;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly LinkGenerator _linkGenerator;
private readonly FormDataService _formDataService;

public UICustodianAccountsController(
DisplayFormatter displayFormatter,
Expand All @@ -46,7 +48,8 @@ public UICustodianAccountsController(
IEnumerable<ICustodian> custodianRegistry,
BTCPayServerClient btcPayServerClient,
BTCPayNetworkProvider networkProvider,
LinkGenerator linkGenerator
LinkGenerator linkGenerator,
FormDataService formDataService
)
{
_displayFormatter = displayFormatter;
Expand All @@ -55,6 +58,7 @@ LinkGenerator linkGenerator
_btcPayServerClient = btcPayServerClient;
_networkProvider = networkProvider;
_linkGenerator = linkGenerator;
_formDataService = formDataService;
}

public string CreatedCustodianAccountId { get; set; }
Expand Down Expand Up @@ -247,7 +251,7 @@ public async Task<IActionResult> EditCustodianAccount(string storeId, string acc

if (configForm.IsValid())
{
var newData = configForm.GetValues();
var newData = _formDataService.GetValues(configForm);
custodianAccount.SetBlob(newData);
custodianAccount = await _custodianAccountRepository.CreateOrUpdate(custodianAccount);
return RedirectToAction(nameof(ViewCustodianAccount),
Expand Down Expand Up @@ -301,7 +305,7 @@ public async Task<IActionResult> CreateCustodianAccount(string storeId, CreateCu
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";
Expand Down
2 changes: 1 addition & 1 deletion BTCPayServer/Controllers/UIPaymentRequestController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ public async Task<IActionResult> ViewPaymentRequestForm(string payReqId, FormVie
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});
Expand Down
2 changes: 1 addition & 1 deletion BTCPayServer/Forms/FormDataExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using BTCPayServer.Abstractions.Form;
using BTCPayServer.Data;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
Expand All @@ -15,6 +14,7 @@ public static void AddForms(this IServiceCollection serviceCollection)
serviceCollection.AddSingleton<IFormComponentProvider, HtmlInputFormProvider>();
serviceCollection.AddSingleton<IFormComponentProvider, HtmlFieldsetFormProvider>();
serviceCollection.AddSingleton<IFormComponentProvider, HtmlSelectFormProvider>();
serviceCollection.AddSingleton<IFormComponentProvider, FieldValueMirror>();
}

public static JObject Deserialize(this FormData form)
Expand Down
47 changes: 42 additions & 5 deletions BTCPayServer/Forms/FormDataService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json.Linq;

namespace BTCPayServer.Forms;

Expand Down Expand Up @@ -150,14 +151,50 @@ public bool IsFormSchemaValid(string schema, [MaybeNullWhen(false)] out Form for

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;
}
}
4 changes: 2 additions & 2 deletions BTCPayServer/Forms/HtmlFieldsetFormProvider.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using BTCPayServer.Abstractions.Form;

namespace BTCPayServer.Forms;
Expand All @@ -13,8 +12,9 @@ public void Register(Dictionary<string, IFormComponentProvider> typeToComponentP
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)
Expand Down
22 changes: 21 additions & 1 deletion BTCPayServer/Forms/HtmlInputFormProvider.cs
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 0 additions & 2 deletions BTCPayServer/Forms/HtmlSelectFormProvider.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
6 changes: 6 additions & 0 deletions BTCPayServer/Forms/IFormComponentProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ public async Task<IActionResult> ViewPointOfSale(string appId,
return RedirectToAction(nameof(ViewPointOfSale), new { appId, viewType });
}

formResponseJObject = form.GetValues();
formResponseJObject = FormDataService.GetValues(form);
break;
}
try
Expand Down Expand Up @@ -406,7 +406,7 @@ public async Task<IActionResult> POSFormSubmit(string appId, FormViewModel viewM
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,
Expand Down
2 changes: 1 addition & 1 deletion BTCPayServer/Views/Shared/Forms/FieldSetElement.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
2 changes: 1 addition & 1 deletion BTCPayServer/Views/Shared/_Form.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
}
Expand Down

0 comments on commit 60d6e98

Please sign in to comment.