
Создайте веб-приложение Contact Manager, которое обновляется в режиме реального времени, используя Butterfly Server для C #
Почти каждое веб-приложение должно обрабатывать базовые операции CRUD (создание, чтение, обновление и удаление). В этом посте мы покажем, как выполнять эти базовые операции CRUD, создав простой диспетчер контактов с интерфейсом Vue.js и сервером Butterfly Server, где изменения автоматически синхронизируются со всеми клиентами.
Хотя в этом примере используется Vue.js, клиент-бабочка - это ванильный javascript, который работает с любым фреймворком.
Конечный результат выглядит так ...

Предполагается, что у вас уже установлены Visual Studio и npm.
Просто позволь мне попробовать
Предпочитаете пропустить пошаговые инструкции?
Запустите это в терминале или в командной строке…
git clone https://github.com/firesharkstudios/butterfly-servercd butterfly-server\Butterfly.Example.Contacts dotnet run
Запустите это во втором терминале или в командной строке…
cd butterfly-server\Butterfly.Example.Contacts\www
npm install
npm run serve
Вы должны увидеть http: // localhost: 8080 / открытым в браузере. Попробуйте открыть второй экземпляр браузера по адресу http: // localhost: 8080 /. Обратите внимание, что изменения автоматически синхронизируются между двумя экземплярами браузера.
Или следуйте приведенным ниже инструкциям, чтобы создать приложение с нуля.
Создание сервера
Мы будем использовать Butterfly Server для создания нашего сервера. У сервера будут две ключевые обязанности ...
- Определите веб-API, который позволяет добавлять, обновлять и удалять контакты.
- Определите API подписки, который позволяет получать список контактов и позволяет получать любые изменения в нашем списке контактов.
Итак, приступим ...
- Создайте новый проект .NET Core Console App в Visual Studio
- В консоли диспетчера пакетов выполните
Install-Package Butterfly.Db Butterfly.Web.EmbedIO
Теперь у нас есть проект и установлены необходимые зависимости.
Затем давайте создадим базовый каркас для Program.cs, который запускает веб-сервер EmbedIO, прослушивающий порт 8000…
using System;
using Butterfly.Db.Memory;
using Butterfly.Util;
using Butterfly.Web.EmbedIO;
using Butterfly.Web.WebApi;
using Dict = System.Collections.Generic.Dictionary<string, object>;
namespace MyCrudApp {
class Program {
static void Main(string[] args) {
using (var ctx = new EmbedIOContext("http://+:8000/")) {
// Create database
// Define Web API
// Define Subscription API
// Start the web server and wait
ctx.Start();
Console.ReadLine();
}
}
}
}
Теперь давайте настроим MemoryDatabase для хранения всех наших контактов.
Примечание. Мы также могли использовать базу данных MySQL, Postgres, SQLite или MS SQL Server с минимальными изменениями в нашем коде.
// Create database
var database = new Butterfly.Core.Database.Memory.MemoryDatabase();
database.CreateFromSqlAsync(@"CREATE TABLE contact (
id VARCHAR(50) NOT NULL,
first_name VARCHAR(40) NOT NULL,
last_name VARCHAR(40) NOT NULL,
PRIMARY KEY(id)
);").Wait();
database.SetDefaultValue("id",
table => $"{table.Abbreviate()}_{Guid.NewGuid().ToString()}"
);
Приведенный выше код будет…
- Создайте экземпляр нашей MemoryDatabase
- Создайте таблицу контактов
- Задайте значение по умолчанию для поля id со значениями типа c_a7329696-a334–4118–88be-e773bb78d2e2
Затем давайте определим наш веб-API…
// Define the Web API
ctx.WebApi.OnPost("/api/contact/insert", async (req, res) => {
var record = await req.ParseAsJsonAsync<Dict>();
await database.InsertAndCommitAsync<string>("contact", record);
});
ctx.WebApi.OnPost("/api/contact/update", async (req, res) => {
var record = await req.ParseAsJsonAsync<Dict>();
await database.UpdateAndCommitAsync("contact", record);
});
ctx.WebApi.OnPost("/api/contact/delete", async (req, res) => {
var id = await req.ParseAsJsonAsync<string>();
await database.DeleteAndCommitAsync("contact", id);
});
Приведенный выше код будет обрабатывать запросы API следующим образом…
- Когда получен POST-запрос к / api / contact / insert, проанализируйте запись из тела как JSON и используйте эту запись для вставки новая запись в таблице контактов
- Когда получен POST-запрос к / api / contact / update, проанализируйте запись из тела как JSON и используйте эту запись для обновления существующая запись в таблице contact
- Когда получен POST-запрос к / api / contact / delete, проанализируйте id из тела как JSON и используйте этот id для удаления существующая запись в таблице contact
Наконец, давайте определим наш Subscription API…
// Define the Subscription API
ctx.SubscriptionApi.OnSubscribe(
"all-contacts",
(vars, channel) => database.CreateAndStartDynamicViewAsync(
"SELECT * FROM contact",
dataEventTransaction => channel.Queue(dataEventTransaction)
)
);
Приведенный выше код…
- Определяет канал всех контактов, на который клиенты могут подписаться
- Когда клиенты подписываются на канал all-contacts, обработчик создает DynamicView, который сначала отправляет клиенту все записи contact, а затем продолжает отправлять клиенту любые изменения в записях контактов
Примечание. Под капотом API подписки использует WebSockets для передачи данных клиенту. Ожидается, что клиенты будут поддерживать открытый WebSocket для сервера. Это происходит автоматически, когда клиенты используют butterfly-client.
На стороне сервера все.
Создание клиента
Давайте воспользуемся npm и @v ue / cli для создания нашего клиентского проекта…
# Install @vue/cli (skip if already installed) npm install -g @vue/cli # Create a skeleton vuetifyjs/pwa project # (accept all the defaults) vue create contacts-client # Install the necessary npm packages cd contacts-client vue add vuetify vue install vue-router butterfly-client
Затем отредактируйте src / main.js, чтобы он выглядел так…
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import vuetify from './plugins/vuetify';
Vue.config.productionTip = false
import { ArrayDataEventHandler, WebSocketChannelClient } from 'butterfly-client'
new Vue({
router,
vuetify,
render: h => h(App),
data() {
return {
channelClient: null,
channelClientState: null,
}
},
methods: {
callApi(url, rawData) {
return fetch(url, {
method: 'POST',
body: JSON.stringify(rawData),
mode: 'no-cors'
});
},
subscribe(options) {
let self = this;
self.channelClient.subscribe({
channel: options.channel,
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://localhost:8000/ws`;
self.channelClient = new WebSocketChannelClient({
url,
onStateChange(value) {
self.channelClientState = value;
}
});
self.channelClient.connect();
},
}).$mount('#app')
Приведенный выше код…
- Создает экземпляр WebSocketChannelClient, который поддерживает соединение WebSocket с нашим Сервером бабочек
- Определяет метод callApi (), который наш клиент может использовать для вызова вызовов API.
- Определяет методы subscribe () и unsubscribe (), которые наш клиент может использовать для подписки / отказа от подписки на определенные каналы на нашем сервере бабочек.
Затем отредактируйте src / App.vue, чтобы он содержал…
<template>
<v-app>
<v-content>
<v-toolbar>
<v-toolbar-title>My CRUD 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.
Затем отредактируйте src / components / HelloWorld.vue, чтобы он содержал…
<template>
<v-container fluid>
<!-- Contact List -->
<v-list v-if="contacts.length>0">
<v-list-item v-for="contact in contacts" :key="contact.id">
<v-list-item-content>
<v-list-item-title>
{{ contact.first_name }} {{ contact.last_name }}
</v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<v-btn icon @click="showDialog(true, contact)">
<v-icon>mdi-edit</v-icon>
</v-btn>
</v-list-item-action>
<v-list-item-action>
<v-btn icon @click="remove(contact.id)">
<v-icon>mdi-delete</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</v-list>
<v-flex class="px-5 py-5 text-xs-center" v-else>
- No Contacts -
</v-flex>
<!-- Add Contact Button -->
<v-flex class="text-xs-center py-3">
<v-btn @click="showDialog(true)" color="primary">
<v-icon>mdi-add</v-icon> Add Contact
</v-btn>
</v-flex>
<!-- Contact Dialog -->
<v-dialog v-model="dialogShow" persistent max-width="500px">
<v-card>
<v-card-title>
<span class="headline" v-if="dialogId">
Update Contact
</span>
<span class="headline" v-else>Add Contact</span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12 sm6>
<v-text-field label="First Name"
v-model="dialogFirstName" autofocus required />
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="Last Name"
v-model="dialogLastName" required>
</v-text-field>
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text
@click="showDialog(false)">Close</v-btn>
<v-btn color="blue darken-1" dark
@click="save">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
</template>
<script>
export default {
data () {
return {
contacts: [],
dialogShow: false,
dialogId: null,
dialogFirstName: null,
dialogLastName: null,
}
},
methods: {
showDialog(show, item) {
this.dialogShow = show;
this.dialogId = (item || {}).id;
this.dialogFirstName = (item || {}).first_name;
this.dialogLastName = (item || {}).last_name;
},
save() {
let data = {
first_name: this.dialogFirstName,
last_name: this.dialogLastName,
};
let apiUrl;
if (this.dialogId) {
apiUrl = 'http://localhost:8000/api/contact/update';
data.id = this.dialogId;
}
else {
apiUrl = 'http://localhost:8000/api/contact/insert';
}
let self = this;
this.$root.callApi(apiUrl, data)
.then(() => self.dialogShow = false);
},
remove(id) {
this.$root.callApi('http://localhost:8000/api/contact/delete', id);
},
},
mounted() {
this.$root.subscribe({
channel: 'all-contacts',
arrayMapping: {
contact: this.contacts,
},
});
},
destroyed() {
this.$root.unsubscribe('all-contacts');
}
}
</script>
Итак, новый HelloWorld.vue выше…
- Подписка на канал все контакты при подключении компонента (синхронизация всех записей контактов на сервере с локальным массивом контактов)
- Отменяется от подписки на канал all-contacts при уничтожении компонента
- Отображает диалоговое окно Контакт, когда пользователь нажимает кнопку Добавить контакт.
- Отображает диалоговое окно Контакт, когда пользователь щелкает значок редактирования на контакте
- Вызывает либо / api / contact / insert, либо / api / contact / update, когда пользователь нажимает кнопку Сохранить в диалоговом окне, передавая соответствующие данные
- Вызывает / api / contact / delete, когда пользователь нажимает значок удаления на контакте
Пробовать это
В Visual Studio запустите серверное приложение. Это запустит сервер и откроет экземпляр браузера по адресу http: // localhost: 8080 /.
Вы даже можете открыть несколько браузеров по адресу http: // localhost: 8080 / и посмотреть, как Butterfly Server синхронизирует всех подключенных клиентов…

Вот и все. Вы хорошо начали создавать веб-приложение CRUD с помощью Butterfly Server.
Резюме
Butterfly Server позволяет создавать гораздо более сложные веб-приложения, в которых клиенты автоматически синхронизируются с сервером. См. Https://butterflyserver.io для получения дополнительной информации.