RemoteUi is a plugin that makes it easier to build complex form editors using .NET and React.
Add the following lines of code to your ASP .NET controller or RPC:
// Define the C# models. public class Contacts { [RemoteUiField("Phone", Descripton = "Your phone number.")] public string Phone { get; set; } [RemoteUiField("Skype", Description = "Your Skype username.")] public string Skype { get; set; } } // Define the C# DTO used for generating remote UI. public class ContactsResponse { // This property denotes the definition used to build a RemoteUi // editor, the definition is generated using RemoteUiBuilder<T>. public JObject Definition { get; set; } // This property denotes the model that is used to populate // RemoteUi editor fields. In other words, this model contains // default property values for the RemoteUi editor. public Contacts Value { get; set; } } // Create a new RemoteUi builder. Register all of your custom nested // models here, that are used in your model tree. var builder = new RemoteUiBuilder<Contacts>() // .Register<ContactsInnerModel>() etc. .Register<Contacts>(); // Return the response from your controller or RPC. return Ok(new ContactsResponse { Definition = builder.Build((IServiceProvider)null), Value = new Contacts { Phone = "12345", Skype = "@skype" } });
Add the following lines to your client React app:
import * as React from "react" import * as ReactDOM from "react-dom"; import 'bootstrap/dist/css/bootstrap.min.css'; import { RemoteUiEditor } from "@remoteui/RemoteUiEditor"; import { RemoteUiEditorStore, RemoteUiDefinition } from "@remoteui/RemoteUiEditorStore"; import { observer } from "mobx-react"; import { observable } from "mobx"; // Define the Typescript models. interface Contacts { phone: string; skype: string; } // Define the API response. interface ContactsResponse { definition: RemoteUiDefinition; value: Contacts; } // Fetch the response and populate the store. const response: ContactsResponse = await fetch(...); const store = new RemoteUiEditorStore(response.definition, response.value); // Display the editor form for the data received from the C# server. const App = observer((props: { }) => { return <RemoteUiEditor store={store} /> }); const root = document.createElement("div"); document.body.append(root); ReactDOM.render(<App />, root); // To validate and send the save request containing the modified // Contacts model to the server, extract the form data as follows: const contacts = await store.getDataAsync();
RemoteUi allows configuring properties of fields in an editor, such as title, description, nullability, group or field type. Supported field types are: String, Integer, CheckBox, Radio, Select, StringList, List, Number, FileBase64, Custom, TextArea, OrderedMultiSelect. RemoteUi is able to infer most types based on C# primitive types from standard library, so in most cases you don't have to specify the type explicitly.
public class SampleRemoteUiDto { [RemoteUiField( "Property title", Type = RemoteUiFieldType.String, Description = "Property description, can contain long messages.", Nullable = false)] public string TextField { get; set; } = string.Empty; [RemoteUiField("CheckBox")] // RemoteUiFieldType.CheckBox public bool BooleanField { get; set; } [RemoteUiField("Integer")] // RemoteUiFieldType.Integer public int IntegerField { get; set; } [RemoteUiField("Number")] // RemoteUiFieldType.Number public double DoubleField { get; set; } }
For a Select field, you can provide possible options via RemoteUiRadioValue attributes:
public class SampleRemoteUiDto { [RemoteUiRadioValue("id-1", "First possible value")] [RemoteUiRadioValue("id-2", "Second possible value")] [RemoteUiField("Property title", Type = RemoteUiFieldType.Select)] public string Option { get; set; } = "id-1"; }
Additionally, RemoteUi allows loading possible Select options dynamically:
public class SampleRemoteUiDto { [SelectOptionProvider] [RemoteUiField("One option from a given set", Type = RemoteUiFieldType.Select)] public string Option { get; set; } = "anon"; public class SelectOptionProvider : RemoteUiCustomRadioValuesAttribute { // The IServiceProvider parameter denotes an instance of the service provider you pass // into the RemoteUiBuilder<T>.Build method when configuring RemoteUi. public override IEnumerable<KeyValuePair<string, string>> Get(IServiceProvider services) { return new Dictionary<string, string> { {"id-1", "First possble value"}, {"id-2", "Second possble value"}, }; } } }
There is also a useful OrderedMultiSelect field type that allows selecting multiple values obtained from a provider that inherits from RemoteUiCustomRadioValues. The values selected via OrderedMultiSelect are kept in a deterministic order defined by a user. See an example:
public class SampleRemoteUiDto { [SelectOptionProvider] [RemoteUiField("Multiple options from a given set", Type = RemoteUiFieldType.OrderedMultiSelect)] public List<string> Options { get; set; } = new List<string>(); public class SelectOptionProvider : RemoteUiCustomRadioValuesAttribute { // The IServiceProvider parameter denotes an instance of the service provider you pass // into the RemoteUiBuilder<T>.Build method when configuring RemoteUi. public override IEnumerable<KeyValuePair<string, string>> Get(IServiceProvider services) { return new Dictionary<string, string> { {"id-1", "First possble value"}, {"id-2", "Second possble value"}, }; } } }
The example above results into the following UI:
In case if you'd like to support multiple languages in your application, you can pass dictionary keys to Title and Description attribute properties when defining a RemoteUiField. Then, pass a displayTransform parameter of type Func<string, string> to the RemoteUiBuilder constructor:
var builder = new RemoteUiBuilder<Contacts>( displayTransform: key => language[key]) .Register<Contacts>();
If you are using a custom naming strategy in your C# APIs, you can override the naming strategy used during RemoteUi definition generation. Just pass a namingStrategy parameter to the RemoteUiBuilder constructor:
var builder = new RemoteUiBuilder<Contacts>( namingStrategy: new CamelCaseNamingStrategy()) .Register<Contacts>();