/directlink

Library for building single-page web applications

Primary LanguageC#OtherNOASSERTION

Library for building single-page web applications that makes the world easier for ASP.NET Core developers who use React.

Features

  • Server-side rendering
  • Real-time communications:
    • invoke .NET methods from React components
    • set state of components from .NET world
  • Flexible server-side routing

Installation

Create ASP.NET Core project and add package:

dotnet new web -o Sample && cd Sample
dotnet remove package Microsoft.AspNetCore.App
dotnet add package Microsoft.AspNetCore.App -v 2.1.5
dotnet add package DirectLink.Core.React

Initialize npm and install packages:

npm init -y
npm install directlink-react
npm install -D @babel/core babel-loader webpack webpack-cli
npm install -D @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties
package.json
{
    "name": "sample",
    "version": "1.0.0",
    "description": "",
    "scripts": {
        "build": "webpack"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "dependencies": {
        "directlink-react": "1.0.5"
    },
    "devDependencies": {
        "@babel/core": "7.1.2",
        "@babel/plugin-proposal-class-properties": "7.1.0",
        "@babel/preset-env": "7.1.0",
        "@babel/preset-react": "7.0.0",
        "babel-loader": "8.0.4",
        "webpack": "4.23.1",
        "webpack-cli": "3.1.2"
    }
}
add webpack.config.js
let path = require('path');

module.exports = {
    mode: 'development',
    entry: { app: 'app.jsx' },
    output: {
        path: path.join(__dirname, 'wwwroot/dist'),
        filename: '[name].js'
    },
    resolve: {
        modules: ['clientApp', 'node_modules']
    },
    externals: {
        'react': 'React',
        'react-dom': 'ReactDOM'
    },
    module: {
        rules: [{
            test: /\.jsx?$/,
            exclude: /node_modules/,
            use: {
                loader: 'babel-loader',
                options: {
                    presets: ['@babel/preset-env', '@babel/preset-react'],
                    plugins: ['@babel/plugin-proposal-class-properties']
                }
            }
        }]
    }
};

Add service and configure pipeline in Startup.cs:

services.AddDirectLink<App>(tags => tags.AddDefaultTemplateTags(title: "Sample"));
app.UseDirectLink(components => components.Map<App>(script: "/dist/app.js"));
Startup.cs
using DirectLinkCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace Sample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDirectLink<App>(tags => tags.AddDefaultTemplateTags(title: "Sample"));
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment()) {
                app.UseDeveloperExceptionPage();
            }

            app.UseDirectLink(components => components.Map<App>(script: "/dist/app.js"));
        }
    }
}

Usage

Add app.jsx to clientApp folder:

components.App = class App extends React.Component {
    constructor(props) {
        super(props);
        directlink.init(this);
    }

    componentWillUnmount() {
        directlink.dispose(this);
    }

    sendMessage = () => {
        this.AddMessage(this.state.message) //here we invoke App.AddMessage method
            .then(() => this.setState({ message: '' }));
    }

    onChange = (event) => {
        this.setState({ [event.target.name]: event.target.value });
    }

    onKeyPress = (event) => {
        if (event.key === 'Enter') {
            this.sendMessage();
        }
    }

    render() {
        return (
            <div className="jumbotron">
                <div className="row mb-3">
                    <div className="col-12 col-sm-8 col-md-9 col-lg-10 mb-3 mb-sm-0">
                        <input type="text" className="form-control" name="message"
                            placeholder="message" autoComplete="off"
                            value={this.state.message} onChange={this.onChange} onKeyPress={this.onKeyPress} />
                    </div>
                    <div className="col-12 col-sm-4 col-md-3 col-lg-2">
                        <button type="button" className="btn btn-primary w-100"
                            onClick={this.sendMessage}>Send</button>
                    </div>
                </div>
                <ul className="list-unstyled">
                    {this.state.Messages.slice().reverse().map(message =>
                        <li key={message.Id}>
                            <div className='alert alert-primary'>{message.Text}</div>
                        </li>)}
                </ul>
            </div>
        );
    }
};

Add Message.cs:

using System;

namespace Sample
{
    public class Message
    {
        public Guid Id { get; }

        public string Text { get; }

        public Message(string text) => (Id, Text) = (Guid.NewGuid(), text);
    }
}

Add App.cs:

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using DirectLinkCore;

namespace Sample
{
    public class App : DirectLinkDispatcher<AppViewModel>
    {
        private static ConcurrentQueue<Message> _messages = new ConcurrentQueue<Message>();

        public IReadOnlyCollection<Message> GetMessages() => _messages;

        public async Task AddMessage(string text)
        {
            _messages.Enqueue(new Message(text));
            await SetStateAsync(new { Messages = _messages }); //here we setState of App component
        }
    }
}

Add AppViewModel.cs:

using System.Collections.Generic;
using DirectLinkCore;

namespace Sample
{
    public class AppViewModel : ViewModel
    {
        public IReadOnlyCollection<Message> Messages { get; }

        public AppViewModel(App app) => Messages = app.GetMessages();
    }
}

Build client and run project:

npm run build
dotnet run

Open http://localhost:5000 in several tabs and check that all clients can text each other.

Documentation

See live samples and documentation for more details.

License

Apache 2.0