Создайте веб-приложение Contact Manager, которое обновляется в режиме реального времени, используя Butterfly Server для C #
Почти каждое веб-приложение должно обрабатывать базовые операции CRUD (создание, чтение, обновление и удаление). В этом посте мы покажем, как выполнять эти базовые операции CRUD, создав простой диспетчер контактов с интерфейсом Vue.js и сервером Butterfly Server, где изменения автоматически синхронизируются со всеми клиентами.
Хотя в этом примере используется Vue.js, клиент-бабочка - это ванильный javascript, который работает с любым фреймворком.
Конечный результат выглядит так ...
Предполагается, что у вас уже установлены Visual Studio и npm.
Просто позволь мне попробовать
Предпочитаете пропустить пошаговые инструкции?
Запустите это в терминале или в командной строке…
git clone https://github.com/firesharkstudios/butterfly-server
cd 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 для получения дополнительной информации.