Given intentions-as-code of a simplest web page, generate its front-end and backend implementations.
- Intentions as code: C# 7 on .NET Core 2
- Front-end: ES6 / JSX, a Hyperapp application
- Backend: C#, an ASP.NET Core 2 application
- On the web page, user enters her name and clicks "Submit" button.
- The name is sent to GetGreetingForName operation in the backend
- The backend invokes Greeting service, which calculates greeting text for specified name.
- Resulting greeting is sent back to the web page
- Web page receives the response and displays the greeting.
Here we use an imaginary domain-specific API (a kind of domain-specific language), which allows describing a web app in code.
Each page in web app is a class derived from WebPage<TModel>, where TModel is the model (or the state) of the page. The only page in our POC is HelloPage:
public class HelloPage : WebPage<HelloPage.HelloModel>A page contains one or more components. Some components are built-in, while others are custom (although the latter are not part of the POC).
A component is included in a page by declaring read-only property like this:
public FormComponent<HelloModel> Form => new FormComponent<HelloModel> {
Model = Model
};What have just happened:
- We included a built-in component
FormComponent<TModel>in our page - We named it
Form(names within one page are unique) - We stated that it is bound to
HelloModel(similarly to pages, every components is bound to a model). - We bound component's
Modelproperty to page'sModel, which means thatFormis bound to the model of the entire page.
Models are classes enriched with semantics metadata. We explain semantics through C# attributes. The HelloModel class looks like this:
public class HelloModel
{
[PropertyContract.Validation.Required(AllowEmpty = false, MaxLength = 50)]
public string Name { get; set; }
[PropertyContract.I18n.Label("ServerSays")]
public string Greeting { get; set; }
}Now that we have a form and a model, and want to send data to server when the form is submitted. For that, we need to describe our backend API. We do it with a read-only property like this:
public IGreetingService GreetingService => GetBackendApiProxy<IGreetingService>();Function GetBackendApiProxy<T> is a marker API that explains our meta-programming logic that GreetingService property represents backend API. IGreetingService is an application-defined interface in our example:
public interface IGreetingService
{
Task<string> GetGreetingForName(string name);
}The last piece that's left to add is an intention to invoke Greeting service, pass it the Name from the model, and assign resulting greeting to the model. Here's how:
public override void Controller()
{
Form.Submitting += async () => Model.Greeting = await GreetingService.GetGreetingForName(Model.Name);
}The Controller method is where we describe interactions between components, the page, and the backend. Here we attach handler to Submitting event of the Form component. As the name implies, this event is fired when the user submits the form.
That's all. The full code listing is in HelloPage.cs
The following packages are part of the test
- Web app expressed through intentions-as-code - Example.App, or go directly to HelloPage class.
- Imaginary web UI programming model (example of domain-specific API) - Example.WebUIModel
- Sample backend generator, which translates UI model into an ASP.NET Core server application - Example.AspNetAdapter
- Sample front-end generator, which translates UI model into a Hyperapp application - Example.HyperappAdapter
- C# language adapter based on Roslyn framework - MetaPrograms.Adapters.Roslyn
- JavaScript / JSX language adapter - MetaPrograms.Adapters.JavaScript
- ExampleAppImplementationTests
- Front-end test case:
CanGenerateFrontEndImplementation - Back-end test case:
CanGenerateBackEndImplementation - Expected outputs
- Front-end test case:
