Drapo is a declarative Single Page Application (SPA) framework that enables you to build dynamic web applications using HTML attributes instead of writing JavaScript. With Drapo, you can implement data binding, control flow, event handling, and complex UI logic directly in your HTML markup using intuitive d-* attributes.
- π·οΈ Declarative HTML: Build interactive UIs using HTML attributes - no JavaScript required
- π Two-way Data Binding: Automatic synchronization between UI and data using mustache syntax
{{}} - π― Control Flow: Built-in
d-forloops andd-ifconditionals - π¨ Dynamic Styling: Conditional CSS classes with
d-class - π‘ Real-time Updates: WebSocket support via SignalR for live data
- π§© Component System: Reusable UI components with lifecycle management
- π£οΈ SPA Routing: Client-side routing for single page applications
- β Validation: Built-in form validation with custom rules
- π¨ Theming: Dynamic theme switching support
- ποΈ .NET Integration: ASP.NET Core middleware for seamless server integration
Install the NuGet package in your ASP.NET Core project:
dotnet add package DrapoOr via Package Manager Console:
Install-Package DrapoAdd Drapo to your ASP.NET Core application in Startup.cs or Program.cs:
// Program.cs (.NET 6+)
builder.Services.AddDrapo();
app.UseDrapo();
// Or Startup.cs (.NET Core 3.1)
public void ConfigureServices(IServiceCollection services)
{
services.AddDrapo();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseDrapo();
}Create an HTML file with Drapo attributes:
<!DOCTYPE html>
<html>
<head>
<script src="/drapo.js"></script>
<title>My First Drapo App</title>
</head>
<body>
<!-- Define data -->
<div d-dataKey="user" d-dataType="object"
d-dataProperty-name="John"
d-dataProperty-age="25"></div>
<!-- Display and edit data -->
<h1>Hello, {{user.name}}!</h1>
<p>Age: <input d-model="{{user.age}}" type="number" /></p>
<!-- Dynamic list -->
<div d-dataKey="items" d-dataType="array"
d-dataProperty-0="Apple"
d-dataProperty-1="Banana"
d-dataProperty-2="Orange"></div>
<ul>
<li d-for="item in items">{{item}}</li>
</ul>
<!-- Event handling -->
<button d-on-click="AddDataItem(items, 'New Item')">Add Item</button>
</body>
</html>Drapo consists of two main components:
- Client-side Framework (TypeScript/JavaScript): Handles DOM manipulation, data binding, and UI logic
- Server-side Middleware (ASP.NET Core): Provides data services, routing, and server integration
βββββββββββββββββββ ββββββββββββββββββββ
β Browser β β ASP.NET Core β
β β β β
β βββββββββββββ β β βββββββββββββββ β
β β HTML β ββββββ€ β Drapo β β
β β + d-* β β β β Middleware β β
β β attributesβ β β βββββββββββββββ β
β βββββββββββββ β β β
β β β βββββββββββββββ β
β βββββββββββββ β β β Your β β
β β drapo.js β ββββββ€ β Controllers β β
β β (Client) β β β β & Services β β
β βββββββββββββ β β βββββββββββββββ β
βββββββββββββββββββ ββββββββββββββββββββ
- Node.js and npm (for development)
- .NET 6+ or .NET Core 3.1+
- TypeScript (for building from source)
npm install -g typescript- Clone the repository:
git clone https://github.com/spadrapo/drapo.git
cd drapo- Build the project:
cd src
dotnet build- Run the demo application:
cd Web/WebDrapo
dotnet runVisit https://localhost:5001 to see the demo application.
| Attribute | Description | Example |
|---|---|---|
d-dataKey |
Defines a data context with a unique key | d-dataKey="user" |
d-dataType |
Specifies the data type (object, array, string, etc.) |
d-dataType="object" |
d-dataProperty-{name} |
Sets initial property values | d-dataProperty-name="John" |
d-dataUrl |
URL for loading data | d-dataUrl="~/api/users" |
d-dataUrlGet |
URL for GET operations | d-dataUrlGet="~/api/users/get" |
d-dataUrlSet |
URL for POST/PUT operations | d-dataUrlSet="~/api/users/save" |
d-model |
Two-way data binding | d-model="{{user.name}}" |
| Attribute | Description | Example |
|---|---|---|
d-for |
Iterates over arrays or objects | d-for="item in items" |
d-if |
Conditional rendering | d-if="{{user.isActive}}" |
d-class |
Dynamic CSS classes | d-class="{active:{{item.selected}}}" |
| Attribute | Description | Example |
|---|---|---|
d-on-click |
Click event handler | d-on-click="SaveData(user)" |
d-on-change |
Change event handler | d-on-change="UpdateTotal()" |
d-on-keyup |
Keyup event handler | d-on-keyup="FilterItems()" |
d-on-keyup-enter |
Enter key specific handler | d-on-keyup-enter="SubmitForm()" |
d-on-blur |
Focus lost event | d-on-blur="ValidateField()" |
| Attribute | Description | Example |
|---|---|---|
d-parent |
Defines the default parent for partial pages | d-parent="main" |
d-parentSector |
Default sector of parent for child content | d-parentSector="content" |
d-child |
Defines default child for a sector | d-child="sidebar" |
d-childSector |
Defines a default child sector | d-childSector="widgets" |
| Attribute | Description | Example |
|---|---|---|
d-dragStart |
Enable drag functionality | d-dragStart="true" |
d-dragEnd |
Enable drop functionality | d-dragEnd="true" |
d-resize |
Enable element resizing | d-resize-location="width" |
d-cloak |
Hide element until data is loaded | d-cloak |
d-viewport |
Enable virtual scrolling for large lists | d-viewport="true" |
Drapo provides many built-in functions for common operations:
| Function | Description | Example |
|---|---|---|
AddDataItem(dataKey, item) |
Add item to array | AddDataItem(users, newUser) |
RemoveDataItem(dataKey, item) |
Remove item from array | RemoveDataItem(users, user) |
PostData(dataKey) |
Save data to server | PostData(users) |
ClearData(dataKey) |
Clear/reset data | ClearData(form) |
CheckDataField(dataKey, field) |
Set field to true | CheckDataField(config, enabled) |
UncheckDataField(dataKey, field) |
Set field to false | UncheckDataField(config, enabled) |
UpdateData(dataKey, data) |
Update entire data object | UpdateData(user, newUserData) |
CreateGuid() |
Generate unique identifier | CreateGuid() |
<!DOCTYPE html>
<html>
<head>
<script src="/drapo.js"></script>
<title>Todo App</title>
</head>
<body>
<!-- Data definitions -->
<div d-dataKey="todos" d-dataType="array"></div>
<div d-dataKey="newTodo" d-dataType="object" d-dataProperty-text=""></div>
<!-- Add new todo -->
<div>
<input d-model="{{newTodo.text}}"
placeholder="What needs to be done?"
d-on-keyup-enter="AddDataItem(todos,newTodo);ClearDataField(newTodo,text)">
<button d-on-click="AddDataItem(todos,newTodo);ClearDataField(newTodo,text)">
Add Todo
</button>
</div>
<!-- Todo list -->
<ul>
<li d-for="todo in todos" d-class="{completed:{{todo.completed}}}">
<input type="checkbox" d-model="{{todo.completed}}">
<span>{{todo.text}}</span>
<button d-on-click="RemoveDataItem(todos,todo)">Delete</button>
</li>
</ul>
<!-- Summary -->
<p d-if="{{todos.length > 0}}">
Total: {{todos.length}} items
</p>
</body>
</html><!-- User form with validation -->
<div d-dataKey="user" d-dataType="object"
d-dataUrlSet="~/api/users/save">
<form>
<div>
<label>Name:</label>
<input d-model="{{user.name}}" required>
</div>
<div>
<label>Email:</label>
<input d-model="{{user.email}}" type="email" required>
</div>
<div>
<label>Country:</label>
<select d-model="{{user.country}}">
<option d-for="country in countries" value="{{country.code}}">
{{country.name}}
</option>
</select>
</div>
<button d-on-click="PostData(user)" type="button">
Save User
</button>
</form>
</div><!-- Load data from server -->
<div d-dataKey="products"
d-dataUrlGet="~/api/products"
d-dataUrl="~/api/products">
<div d-if="{{products.length == 0}}">
Loading products...
</div>
<div d-for="product in products" class="product-card">
<h3>{{product.name}}</h3>
<p>Price: ${{product.price}}</p>
<button d-on-click="AddToCart(product)">Add to Cart</button>
</div>
</div>Drapo automatically validates WebSocket connection origins to prevent Cross-Site WebSocket Hijacking (CSWSH) attacks. By default, only connections from the same origin as your application are allowed.
You can configure WebSocket origin validation in your Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseDrapo(options =>
{
// Enable/disable origin validation (enabled by default)
options.Config.ValidateWebSocketOrigin = true;
// Optional: Specify allowed origins explicitly
// Note: Scheme (http/https) is ignored during validation
options.Config.AllowedWebSocketOrigins = new List<string>
{
"https://yourdomain.com",
"https://www.yourdomain.com",
"https://subdomain.yourdomain.com"
};
});
}Gateway Support: The origin validation automatically supports scenarios where an external gateway or load balancer handles HTTPS, while the internal application runs on HTTP. Only the host/domain portion is compared, not the scheme.
- Origin validation is enabled by default for security
- The current request's host is always allowed (scheme-independent to support gateways)
- If an explicit allow-list is configured, origins in that list are also allowed (in addition to the current host)
- Both
OriginandRefererheaders are checked - Invalid or missing headers result in connection rejection
- Scheme is ignored during validation - only the host/domain is compared, allowing gateways to handle HTTPS while the internal application uses HTTP
options.Config.ValidateWebSocketOrigin = false;Drapo includes a comprehensive testing framework using Selenium WebDriver and NUnit to validate the framework's functionality across different browsers and scenarios.
The testing system consists of:
- Pages Folder (
src/Test/WebDrapo.Test/Pages/): Contains HTML test pages with expected evaluated HTML output - ReleaseTest Class (
src/Test/WebDrapo.Test/ReleaseTest.cs): Initializes the WebDriver, defines test cases, and validation methods - Test Settings File (
Test.runsettings): Runtime configuration including URLs for the WebDrapo instance
-
Prerequisites: Ensure you have Chrome WebDriver installed and WebDrapo running locally
-
Configuration: Update the URL in
Test.Debug.runsettingsto match your local WebDrapo instance:<Parameter name="webAppUrl" value="https://localhost:5001/" />
-
Run Tests: Execute tests via Visual Studio Test Explorer or command line:
dotnet test src/Test/WebDrapo.Test/
For each new page added to DrapoPages, follow these steps to create corresponding tests:
-
Start WebDrapo: Run the application in debug mode and verify the URL matches your test settings
-
Configure Test Settings: In Visual Studio, go to Test Menu β Test Settings and select the appropriate
.runsettingsfile -
Create Test Page: Add a new HTML file in the
Pagesfolder with naming format:{PageName}.Test.html- Set the file's Build Action to "Embedded Resource"
-
Create Test Method: In
ReleaseTest.cs, add a new test method:[TestCase] public void YourPageNameTest() { ValidatePage("YourPageName"); }
-
Generate Expected Output:
- Run the test in debug mode
- After WebDriver navigation, copy the
PageSourcecontent - Paste it into your test HTML file (without formatting)
- Save and re-run the test to verify it passes
The test suite covers various Drapo features:
- Data Binding: Two-way binding, model updates, data synchronization
- Control Flow:
d-forloops,d-ifconditionals, nested structures - Events: Click handlers, keyboard events, form interactions
- Components: Component lifecycle, data passing, sector management
- Validation: Form validation, custom rules, error handling
- Advanced Features: Drag & drop, resizing, viewport scrolling
We welcome contributions to Drapo! Here's how to get started:
-
Fork and Clone:
git clone https://github.com/your-username/drapo.git cd drapo -
Install Dependencies:
cd src/Middleware/Drapo npm install -
Build the Project:
cd src dotnet build -
Run Tests:
dotnet test
- Create a Feature Branch:
git checkout -b feature/your-feature-name - Make Changes: Implement your feature or bug fix
- Add Tests: Ensure your changes are covered by tests
- Build and Test: Verify everything works correctly
- Submit Pull Request: Create a PR with a clear description
- TypeScript: Follow the existing TSLint configuration
- C#: Use standard .NET conventions
- HTML: Use consistent indentation and attribute ordering
- Commit Messages: Use clear, descriptive commit messages
When reporting bugs or requesting features:
- Check existing issues first
- Provide a minimal reproduction case
- Include browser and .NET version information
- Describe expected vs actual behavior
- Live Demo - Interactive examples and playground
- API Reference - Complete attribute and function documentation
- Examples - Common usage patterns and code samples
- d-for Documentation - Detailed loop syntax guide
- NuGet Package:
Drapo - Supported Frameworks:
- .NET 8.0
- .NET 6.0
- .NET Core 3.1
- Browser Support: Modern browsers (Chrome, Firefox, Safari, Edge)
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
- Built with ASP.NET Core and TypeScript
- Uses SignalR for real-time communication
- Inspired by modern declarative UI frameworks
- Thanks to all contributors and users of the Drapo framework
Made with β€οΈ by the Drapo team