В этом посте будет показано, как создать веб-приложение в реальном времени, в котором все изменения немедленно обновляются во всех клиентах.
В этом примере используется…
- Vuetify и Vue.js на клиенте
- Butterfly Server .NET и EmbedIO на сервере
Просто дай мне попробовать
Предпочитаете пропускать пошаговые инструкции?
Запустите это в терминале или командной строке…
git clone https://github.com/firesharkstudios/butterfly-server-dotnet
cd butterfly-server-dotnet\Butterfly.Example.Todo dotnet run -vm
Запустите это во втором терминале или командной строке…
cd butterfly-server-dotnet\Butterfly.Example.Todo\vue
npm install
npm run dev
Вы должны увидеть http://localhost:8080/ открытым в браузере. Попробуйте открыть второй экземпляр браузера по адресу http://localhost:8080/. Обратите внимание, что изменения автоматически синхронизируются между двумя экземплярами браузера.
Или следуйте приведенным ниже инструкциям, чтобы создать приложение с нуля.
Создание сервера
Во-первых, давайте создадим новый проект Консольное приложение .NET Core в Visual Studio.
Затем откройте Консоль диспетчера пакетов в Visual Studio и установите зависимости…
Install-Package Butterfly.Core Install-Package Butterfly.EmbedIO
Затем отредактируйте Program.cs, добавив следующие операторы импорта…
using System; using Butterfly.Core.Channel; using Butterfly.Core.Database; using Butterfly.Core.Util; using Butterfly.Core.WebApi; using Dict = System.Collections.Generic.Dictionary<string, object>;
Затем отредактируйте метод Main() в Program.cs, чтобы он был…
static void Main(string[] args) { // Create the underlying EmbedIOWebServer var webServer = new Unosquare.Labs.EmbedIO.WebServer(8000); Unosquare.Swan.Terminal.Settings.DisplayLoggingMessageType = Unosquare.Swan.LogMessageType.Info; // Create a MemoryDatabase (no persistence, limited features) var database = new Butterfly.Core.Database.Memory.MemoryDatabase(); // Setup webApiServer and channelServer using embedIOWebServer using (var webApi = new Butterfly.EmbedIO.EmbedIOWebApi(webServer)) using (var subscriptionApi = new Butterfly.EmbedIO.EmbedIOSubscriptionApi(webServer)) { Init(database, webApi, subscriptionApi); webApi.Compile(); subscriptionApi.Start(); webServer.RunAsync(); Console.ReadLine(); } }
Приведенный выше метод Main()…
- Создает веб-сервер EmbedIO для прослушивания запросов HTTP и WebSocket на порту 8000.
- Создает базу данных в памяти
- Создает WebApiServer и ChannelServer, которые обертывают веб-сервер EmbedIO.
- Запускает веб-сервер EmbedIO
Затем добавьте метод Init() в Program.cs, чтобы он был…
static void Init(IDatabase database, IWebApi webApi, ISubscriptionApi subscriptionApi) { // Setup database database.CreateFromTextAsync(@"CREATE TABLE todo ( id VARCHAR(50) NOT NULL, name VARCHAR(40) NOT NULL, PRIMARY KEY (id) );").Wait(); database.SetDefaultValue("id", table => $"{table.Abbreviate()}_{Guid.NewGuid().ToString()}"); // Listen for API requests webApi.OnPost("/api/todo/insert", async (req, res) => { var todo = await req.ParseAsJsonAsync<Dict>(); await database.InsertAndCommitAsync<string>("todo", todo); }); webApi.OnPost("/api/todo/delete", async (req, res) => { var id = await req.ParseAsJsonAsync<string>(); await database.DeleteAndCommitAsync("todo", id); }); // Listen for subscribe requests... // - The handler must return an IDisposable object // (gets disposed when the channel is unsubscribed) // - The handler can push data to the client by // calling channel.Queue() subscriptionApi.OnSubscribe("todos", (vars, channel) => { return database.CreateAndStartDynamicViewAsync( "todo", dataEventTransaction => channel.Queue(dataEventTransaction) ); }); }
Приведенный выше метод Init()…
- Создает таблицу todo в базе данных со случайным полем идентификатора GUID.
- Прослушивает запросы POST в /api/todo/insert, которые вставляют новую запись в таблицу todo.
- Прослушивает запросы POST в /api/todo/delete, которые удаляют существующую запись в таблице todo.
- Отслеживает запросы на подписку на канале todos, который возвращает все существующие записи todo и любые изменения в записях todo
Это все для серверной части.
Создание клиента
Давайте воспользуемся npm для создания нашего клиентского проекта…
npm install -g vue-cli # Just accept the defaults... vue init vuetifyjs/pwa my-todo-client cd my-todo-client npm install npm install butterfly-client reqwest
Приведенные выше команды…
- Создайте исходный проект, используя Шаблон Vuetify PWA.
- Установите бабочку-клиент для связи с нашим Butterfly Server .NET
- Установите библиотеку reqwest, чтобы выполнять вызовы API.
Затем отредактируйте config/index.js, заменив proxyTable: {} на…
proxyTable: { '/api': { target: 'http://localhost:8000/', changeOrigin: true, }, '/ws': { target: 'http://localhost:8000/', changeOrigin: true, ws: true, } }
Вышеупомянутые изменения позволят серверу разработки узла передавать запросы API и WebSocket нашему Butterfly Server .NET.
Затем отредактируйте src/main.js, чтобы добавить пару импортов…
import { ArrayDataEventHandler, WebSocketChannelClient } from 'butterfly-client' import reqwest from 'reqwest'
Затем отредактируйте src/main.js, заменив существующий вызов new Vue() на…
new Vue({ el: '#app', router, components: { App }, template: '<App/>', data() { return { channelClient: null, channelClientState: null, } }, methods: { callApi(url, rawData) { return reqwest({ url, method: 'POST', data: JSON.stringify(rawData), }); }, subscribe(options) { let self = this; self.channelClient.subscribe({ channel: options.key, vars: options.vars, handler: new ArrayDataEventHandler({ arrayMapping: options.arrayMapping, onInitialEnd: options.onInitialEnd, onChannelMessage: options.onChannelMessage }), }); }, unsubscribe(key) { let self = this; self.channelClient.unsubscribe(key); }, }, beforeMount() { let self = this; let url = `ws://${window.location.host}/ws`; self.channelClient = new WebSocketChannelClient({ url, onStateChange(value) { self.channelClientState = value; } }); self.channelClient.connect(); }, })
Приведенный выше код…
- Создает экземпляр WebSocketChannelClient, который поддерживает соединение WebSocket с нашим Butterfly Server .NET.
- Определяет метод callApi(), который наш клиент может использовать для вызова вызовов API.
- Определяет методы subscribe() и unsubscribe(), которые наш клиент может использовать для подписки/отмены подписки на определенные каналы на нашем Butterfly Server .NET.
Затем отредактируйте src/App.vue, чтобы он содержал…
<template> <v-app> <v-content> <v-toolbar> <v-toolbar-title>My Todo Example</v-toolbar-title> <v-spacer /> </v-toolbar> <router-view v-if="$root.channelClientState=='Connected'"/> <div class="px-5 py-5 text-xs-center" v-else> <v-progress-circular indeterminate color="primary"/> <span class="pl-2 title"> {{ $root.channelClientState }}... </span> </div> </v-content> </v-app> </template>
Приведенный выше шаблон заставит основное содержимое нашей страницы отображать индикатор загрузки до тех пор, пока наш WebSocketChannelClient не будет успешно подключен к нашему Butterfly Server .NET.
Затем отредактируйте src/components/HelloWorld.vue, указав…
<template> <v-container fluid> <Todos/> </v-container> </template> <script> import Todos from '@/components/Todos' export default { components: { Todos, } } </script>
Затем создайте новый src/components/Todos.vue, который содержит…
<template> <div> <div class="px-3 py-3 text-xs-center" v-if="items.length==0"> No todos yet </div> <v-list v-else> <Todo v-for="item in items" :key="item.id" :item="item" @remove="remove" /> </v-list> <div class="px-3 py-3 text-xs-center"> <v-btn color="primary" @click="add">Add Todo</v-btn> </div> </div> </template> <script> import Todo from '@/components/Todo' export default { components: { Todo, }, data () { return { items: [], } }, methods: { add() { this.$root.callApi('/api/todo/insert', { name: 'A new todo item', }); }, remove(id) { this.$root.callApi('/api/todo/delete', id); }, }, mounted() { let self = this; self.$root.subscribe({ arrayMapping: { todo: self.items, }, key: 'todos', }); } } </script>
Вышеупомянутый Todos.vue отвечает за…
- Вызов функции add() для выполнения вызова API к нашему Butterfly Server .NET, который добавляет новый todo
- Вызов функции remove() для выполнения вызова API к нашему Butterfly Server .NET, который удаляет существующую todo
- Подписка на канал todos на нашем Butterfly Server .NET и сопоставление записей todo с локальным массивом items
Затем создайте новый src/components/Todo.vue, который содержит…
<template> <v-list-tile> <v-list-tile-content> <v-list-tile-title>{{ item.name }}</v-list-tile-title> </v-list-tile-content> <v-list-tile-action> <v-btn icon @click="$emit('remove', item.id)"> <v-icon>delete</v-icon> </v-btn> </v-list-tile-action> </v-list-tile> </template> <script> export default { props: { item: null } } </script>
Вышеупомянутый Todo.vue отвечает за отрисовку одного todo.
Наконец, отключите eslint, закомментировав этот раздел в build/webpack.base.conf.js…
/* { test: /\.(js|vue)$/, loader: 'eslint-loader', enforce: 'pre', include: [resolve('src'), resolve('test')], options: { formatter: require('eslint-friendly-formatter') } }, */
пробовать это
Конечный результат будет выглядеть примерно так…
Чтобы попробовать приложение Todo List…
- Запустите сервер в Visual Studio
- В терминале запустите
npm run dev
, находясь в каталоге my-todo-client.
Окно браузера автоматически откроется с адресом http://localhost:8080. Откройте другое окно браузера по адресу http://localhost:8080 и обратите внимание, что элементы todo остаются синхронизированными, когда вы добавляете/удаляете элементы todo в любом окне браузера.
Следующие шаги
Butterfly Server .NET поддерживает создание гораздо более сложных веб-приложений в реальном времени (может подписываться на каналы с несколькими наборами данных, может объединять несколько таблиц в каждом наборе данных и т. д.). См. GitHub для более подробной информации.