Пример реализации BFS на C#

Алгоритм поиска в ширину (Breadth-First Search, BFS) используется для поиска или обхода узлов в графе или дереве. BFS начинает с корневого узла и исследует соседние узлы на каждом уровне, прежде чем переходить к узлам следующего уровня.

Ниже приведен простой пример реализации алгоритма BFS на C#. В этом примере мы будем использовать неориентированный граф, представленный в виде списка смежности.

Пример реализации BFS на C#

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // Создаем граф как словарь списков смежности
        Dictionary<int, List<int>> graph = new Dictionary<int, List<int>>()
        {
            {0, new List<int> {1, 2}},
            {1, new List<int> {0, 3, 4}},
            {2, new List<int> {0, 5, 6}},
            {3, new List<int> {1}},
            {4, new List<int> {1}},
            {5, new List<int> {2}},
            {6, new List<int> {2}}
        };

        // Вызываем BFS начиная с узла 0
        BFS(graph, 0);
    }

    static void BFS(Dictionary<int, List<int>> graph, int startNode)
    {
        // Очередь для хранения узлов, которые нужно посетить
        Queue<int> queue = new Queue<int>();
        // Хэш-набор для отслеживания посещенных узлов
        HashSet<int> visited = new HashSet<int>();

        // Начинаем с добавления начального узла в очередь и помечаем его как посещенный
        queue.Enqueue(startNode);
        visited.Add(startNode);

        while (queue.Count > 0)
        {
            // Извлекаем узел из очереди
            int currentNode = queue.Dequeue();
            Console.WriteLine("Посещаем узел: " + currentNode);

            // Проходим по всем соседям текущего узла
            foreach (int neighbor in graph[currentNode])
            {
                if (!visited.Contains(neighbor))
                {
                    // Если сосед не был посещен, добавляем его в очередь и помечаем как посещенный
                    queue.Enqueue(neighbor);
                    visited.Add(neighbor);
                }
            }
        }
    }
}

Объяснение кода

1. Граф как словарь списков смежности: Граф представлен как словарь, где ключом является узел, а значением — список соседних узлов. Например, узел 0 имеет соседей 1 и 2.

      2. Функция BFS:

      • Принимает граф и начальный узел.
      • Использует очередь (Queue<int>) для хранения узлов, которые нужно посетить, и множество (HashSet<int>) для отслеживания посещенных узлов.
      • Начинает с добавления начального узла в очередь и помечает его как посещенный.
      • Пока очередь не пуста, узел извлекается из очереди, и его соседи проверяются. Если соседний узел еще не был посещен, он добавляется в очередь и помечается как посещенный.

      3. Запуск программы:

      • Программа начинает обход графа с узла 0 и посещает все узлы в графе в порядке BFS.

      Результат выполнения программы

      Посещаем узел: 0
      Посещаем узел: 1
      Посещаем узел: 2
      Посещаем узел: 3
      Посещаем узел: 4
      Посещаем узел: 5
      Посещаем узел: 6

      Как мы подняли наш сайт-агрегатор на домашнем ноутбуке

      В общем, задача протестировать MVP (minimum viable product), это специальный подход в it индустрии, когда делают простой проект быстро, тестируют на аудитории, получают результат, а дальше принимается решение расти или нет.

      У нас была такая же мысль с MVP. Но где разворачивать сайт? Хотели использовать яндекс облако, но они откровенно не дешевые для MVP, в принципе есть бесплатный период теста, но опять же, крутить сайт, пусть даже за 5 тыс руб. в месяц без единого дохода так себе.

      Решение — держать сайт на ноутбуке. Я считаю такой подход в разы лучше. Т.к. мы получаем больше ресурсов чем минимальный тариф в облаке. Более того такой вариант можно недорого масштабировать, конечно если вы готовы возиться с железками. Либо масштабировать до логического конца, а потом перейти в облако.

      Нужно ли мне отчитываться перед государством, если я подниму сайт дома?

      Скажу честно, не знаю откуда взялась эта довольно идиотская байка. Для государства нет никакой разницы арендуете ли вы сайт в облаке или развернули у себя. Если есть заработок, платите налоги — всё, никаких претензий.

      Настройка ноутбука под сайт

      Что необходимо:

      1. Более менее нормальный интернет (я давно использую 300 Мбит/с)
      2. Статический адрес (арендовать у провайдера за 150 руб. в месяц)
      3. Старый ноутбук.
      4. Домашний роутер (лучше гигабитный)

      Статический адрес

      Статический IP адрес — по сути адрес вашего домашнего/офисного интернет оборудования для общения с вашим провайдером. При первом подключении к провайдеру, провайдер не выдает индивидуальный IP адрес, он формируется динамически, время от времени менятся. Поэтому если настроить сайт на такой адрес, то после изменений ваш сайт просто не найдут. Но в сети есть варианты как настроить бесплатный статический адрес с хитростями. Но мне было лень это изучать и как-то не по-взрослому, ты зависишь от каких-то сторонних сервисов и о надежности не приходиться говорить. Мне проще заплатить 150 рублей в месяц.

      Настройка статического адреса

      Небольшая вводная история. Я еще года 2 — 3 назад хотел настроить себе статич. адрес для игры в CS 1.6 🙂 В общем думаю — “ну фигня вопрос!”. Заказываю статический IP адрес, как вы думаете у кого, у ростелекома. Ага, значит пытаюсь настроить, всё делаю по феншую, но никак не могу запустить. Звоню узнать — аХа, щас. Ихние спецы — полный ноль. Звонил раз 5. Короче “выдающиеся” ничем мне не помогли, переписывался даже в VK, к сожалению не могу приложить переписку, удалил от злости и заблокировал их. Короче, начал сомневаться в своих когнитивных способностях. Как это обычно бывает, решил прибегнуть к кардинальному решению — сменить к чертям «надежного» провайдера. В итоге всё заработало с первого раза.

      В принципе роутер настраивается просто, сами интерфейсы роутера сильно разные, поэтому я просто опишу куда смотреть на примере tp-link.

      1. Вам нужно добавить статический адрес, который либо выдает оператор в договоре либо можно посмотреть в личном кабинете провайдера или просто увидеть его через myIp.ru

      2. Далее указываем внутри вашего роутера, куда переадресовываться трафик. В моём случае “Тип сервер” произвольное название. “Внешний порт” — это как раз потр для сайта, обычно это 80, указываем его. А вот для CS 1.6 порт по умолчанию = 27015. “Внутренний IP” — это IP вашей машины дома. Таким образом можно подключить любое количество компьютеров, которые будут “общаться” внутри только вашей сети, а внешний доступ иметь по единственному, дефолтному порту. Остальные настройки произвольные.

      Внутренний IP машины на ms windows можно посмотреть через команду ipconfig.

      3. Тут необязательные настройки. Чтобы внутренний адрес вашей машины не менялся произвольно, когда ему захочется. Привяжите MAC-адрес (уникальный адрес сетевого устройства, задается еще на заводе) своей машины и её текущий адрес

      Поднимаем сайт на ноутбуке

      Если у вас windows это делается очень просто. Всё что вам нужно это запустить веб-сервер (IIS). В windows он встроен. По-моему, его нет только в windows home basic. В русской версии это “IIS Службы”. Выберите Панель управления (Control Panel) > Программы и компоненты (Programs and Features) > Включение или отключение компонентов Windows (Turn Windows Features on or off). Разверните узел Службы IIS (Internet Information Services).

      Нажмите окей, после некоторой загрузки откройте браузер и напишите в поисковой строке localhost или явно укажите порт 80 — localhost:80. Откроется тестовая страница. Поздравляю вы запустили сайт. С помощью IIS веб-сервера можно запускать сайты, например, на WordPress или много другое.

      Мы же конечно используем более серьезный стек на сайте. Это Microsoft dotnet работает на linux-е. Кстати да, может вы всё еще не знаете, но Microsoft любит линукс. И уже с 2016-го года выпускают свой программный флагман dotnet под все платформы, т.е. кроссплатформенно. Для фронтенда у нас Angular последних версий. Также есть CI/CD на Azure DevOps, он бесплатный до 5 человек.

      Уникальный дизайн приложению не нужен

      Если лень читать. Напишу сразу, используйте готовые решения, готовый дизайн такой как material design (https://material.io/develop/android). Его можно использовать как для мобильного приложения так и для веб-сайта.

      Почему дизайн приложения — зло

      Если вы решили сделать свое уникальное приложение вам необходимо посчитать цену и затраты. Вообще сделать дизайн относительно несложно, а визуального эффекта от него очень много. Как-то давно мой товарищ обратился в веб-студию заказать сайт, наподобие авто.ру (такой же сложный с технической точки зрения). Подписали договор и начали работу. Дизайн он получил очень быстро, ну казалось бы, эффектно и сделали быстро. Но когда дело дошло до технической реализации все затянулось на год и ничего не работало, как итог суд. Деньги отсудили назад. Поэтому дизайн легче и проще продать и тем более сделать. Его часто преподносят как уникальную и крайне важную часть вашего It проекта. Когда я слышу про: «аля уникальный дизайн приложения», «только вы и только для вас». Но по факту — это бессмысленная трата денег и времени. Пример дизайна из интернета — стандартные котролы для бизнеса, как видно, ничего уникального.

      Как видно абсолютно стандартные котролы

      Чтобы не тянуть «яйца за кота» скажу сразу, что для дизайна бизнес приложения есть очень крутые, готовые котролы (можно сказать фреймворк дизайн, напримре material https://material.io/develop/android). Это можно сказать, дизайн от google. Но в любом случае, вы можете найти кучу готового дизайна. Просто скажите программисту: используй какой-нибудь готовый фреймворк дизайна, такой как — material design.

      Android Material Design - Freebiesbug

      UX дизайн — не дизайн

      Часто слышу такое словосочетание как «UX дизайн». UX (анг. user experience) если вкратце — это максимально удобное расположение которолов и максимально понятное использование. Но не внешний вид этих контролов. Например, уже все привыкли, что в операционной системе windows кнопочка «закрыть» всегда располагается в правом верхнем углу.

      Если вы перейдете на Linux (Ubuntu) или MacOS, то будете сбиты столку тем, что кнопки «закрыть» в этих вариантах ОС слева.

      Знакомство с программой Finder на компьютере Mac - Служба ...

      Его тоже могут подсунуть вам как важную часть приложения. В принципе правильный UX очень важен. Но смотрите, все приложения более менее одинаковые в пользовательском смысле. Придерживайтесь стандартам и на первом этапе этого будет достаточно. И это правильно если вы купили BMW, а теперь передвигаетесь на Mercedes, то у вас не будет проблем с расположением руля.

      Как сэкономить деньги при разработке приложения или сайта.

      Как итог сэкономить деньги можно на дизайне и даже на UX дизайне. Не заказывайте дизайн вообще, уникальный дизайн приложению не нужен, а уникальное расположение котролов может даже вредить. Пользователи привыкли к одному, а вы им подсовываете другое.

      Использование SignalR с JavaScript

      В этой статье я покажу базовые навыки построения приложения в реальном времени используя SignalR.

      • Создаем веб-проект
      • Добавить клиентскую библиотеку SignalR
      • Добавить хаб SignalR
      • Сконфигурируем проект, чтобы запустить SignalR в классе Startup.
      • Добавим код, который отсылает сообщения клиентским приложениям.
      пример приложения signalr на javascript

      Создаем веб-проект

      • В VisualStudio выберите File > New Project
      • В диалоговом окне «Create a new project» выберите «ASP.NET Core Web Application«, и нажмите Next.
      • В диалоговом окне «Configure your new project«, назовите проект, например «SignalRJavaScript«, и нажмите Create.
      • В диалоговом окне «Create a new ASP.NET Core web Application«, выберите .NET Core и ASP.NET Core 3.0.
      • Выберите Web Application чтобы создать проект на основе рейзор страниц (Razor Pages), и далее нажмите Create.

      Добавить клиентскую библиотеку SignalR

      Сама инфраструктура SignalR уже влючина в APS.NET Core 3.0. Но клиентской библиотеки нет. Поэтому запустите библиотечного менеджера (LibMan) и подгрузите библиотеку unpkg.

      • Перейдите на Обозреватель решений / Solution Explorer щелкните по проекту правой кнопкой и выберите Add > Client-Side Library.
      • В диалоге «Add Client-Side Library» в секции провайдер / Provider выберите «unpkg»
      • Чтобы подгрузить клиентскую библиотеку наберите название » @microsoft/signalr@latest«

      Добавить хаб SignalR

      Все классы унаследованые от класса Hub это высокоуровневая обертка над клиент-серверной комуникацией.

      • Добавим в корень проекта свой класс ChatHub.cs с кодом внутри.
      using System.Threading.Tasks;
      using Microsoft.AspNetCore.SignalR;
      
      namespace SignalRJavaScript
      {
          public class ChatHub : Hub
          {
              public async Task SendMessage(string user, string message)
              {
                  await Clients.All.SendAsync("ReceiveMessage", user, message);
              }
          }
      }
      

      Сам класс Hub управляет соединениями, группами и сообщениями. Обратите внимание на метод SendMessage. Данный метод кастомный т.е. вы можете назвать его как угодно. Данный метод после компиляции появиться в JavaScript-е и из этого скрипта мы сможем вызвать его. Т.е. вызвать SendMessage на стороне клиента. В свою очередь метод SendAsync запускает событиеReceiveMessage к которому можно подписаться на стороне клиента.

      Сконфигурируем проект, чтобы запустить SignalR в классе Startup.

      Добавьте две строчки:
      1. services.AddSignalR();
      2. endpoints.MapHub<ChatHub>(«/chatHub»);

      using Microsoft.AspNetCore.Builder;
      using Microsoft.AspNetCore.Hosting;
      using Microsoft.Extensions.Configuration;
      using Microsoft.Extensions.DependencyInjection;
      using Microsoft.Extensions.Hosting;
      
      namespace SignalRJavaScript
      {
          public class Startup
          {
              public Startup(IConfiguration configuration)
              {
                  Configuration = configuration;
              }
      
              public IConfiguration Configuration { get; }
      
              // This method gets called by the runtime. Use this method to add services to the container.
              public void ConfigureServices(IServiceCollection services)
              {
                  services.AddRazorPages();
                  services.AddSignalR(); <---------- 1.
              }
      
              // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
              public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
              {
                  if (env.IsDevelopment())
                  {
                      app.UseDeveloperExceptionPage();
                  }
                  else
                  {
                      app.UseExceptionHandler("/Error");
                      // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                      app.UseHsts();
                  }
      
                  app.UseHttpsRedirection();
                  app.UseStaticFiles();
      
                  app.UseRouting();
      
                  app.UseAuthorization();
      
                  app.UseEndpoints(endpoints =>
                  {
                      endpoints.MapRazorPages();
                      endpoints.MapHub<ChatHub>("/chatHub"); <---------- 2.
                  });
              }
          }
      }
      

      Добавим код, который отсылает сообщения клиентским приложениям.

      В файл Pages/index.cshtml добавьте следующую разметкую

      @page
      @model IndexModel
      @{
          ViewData["Title"] = "Home page";
      }
      
      <div class="text-center">
          <h1 class="display-4">Welcome</h1>
          <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
      </div>
      
      <div class="container">
          <div class="row"> </div>
          <div class="row">
              <div class="col-2">User</div>
              <div class="col-4"><input class="form-control" type="text" id="userInput" /></div>
          </div>
          <div class="row">
              <div class="col-2">Message</div>
              <div class="col-4"><textarea class="form-control" type="text" id="messageInput"></textarea></div>
          </div>
          <div class="row"> </div>
          <div class="row">
              <div class="col-6">
                  <input class="btn btn-primary" type="button" id="sendButton" value="Send Message" />
              </div>
          </div>
      </div>
      <div class="row">
          <div class="col-12">
              <hr />
          </div>
      </div>
      <div class="row">
          <div class="col-6">
              <ul id="messagesList"></ul>
          </div>
      </div>
      <script src="~/lib/@@microsoft/signalr/dist/browser/signalr.js"></script>
      <script src="~/js/chat.js"></script>

      Добавьте файл JavaScript chat.js с кодом.

      "use strict";
      
      // Получим наш объект "соединение"
      var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
      
      // Кнопка будет неактивная, до тех пор, пока нет соединения.
      document.getElementById("sendButton").disabled = true;
      
      // Навешиваем событие click на элемент с id = "sendButton"
      // Т.е. когда мы кликаем по кнопке, то запускается наша функция обраного вызова.
      document.getElementById("sendButton").addEventListener("click", function (event) {
      	// Берем тексты из контролов <input />
          var user = document.getElementById("userInput").value;
          var message = document.getElementById("messageInput").value;
      	
      	// Тут черзе объект connection фактически запускаем метод "SendMessage" на сервере, 
      	// который в свою очередь с сервера оповестит всех подписчиков методом "ReceiveMessage".
          connection.invoke("SendMessage", user, message).catch(function (err) {
              return console.error(err.toString());
          });
      	
          event.preventDefault();
      });
      
      // Подписываемся к событию "ReceiveMessage". 
      // Данное событие запускается на сервере (в коде c# это метод "ReceiveMessage"), а js "слушает его"
      connection.on("ReceiveMessage", function (user, message) {
      	// Как только событие отработает запуститься наша функция обратного вызова callback function
          var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
          var encodedMsg = user + " says " + msg;
          var li = document.createElement("li");
          li.textContent = encodedMsg;
          document.getElementById("messagesList").appendChild(li);
      });
      
      // Запускаем соединение асинхронно.
      connection.start().then(function () {
      	// Если соединение успешное установлено, то активируем кнопку
          document.getElementById("sendButton").disabled = false;
      }).catch(function (err) {
      	// Если соединение не удалось
          return console.error(err.toString());
      });
      
      
      • var connection = new signalR.HubConnectionBuilder().withUrl(«/chatHub»).build(); — получаем наш хаб.
      • connection.invoke(«SendMessage», user, message) — отправляем сообщение
      • connection.on(«ReceiveMessage», function(user, msg ){}) — слушаем событие ReceiveMessage, которое запускается на сервере.

      Yandex Cloud (too many connections for role)

      При папытке работы с Yandex cloud postgresql. Постоянно получал ошибку » too many connections for role» или «The connection pool has been exhausted».

      Причина

      Терпеть немогу этот ваш ДевОпс. Так или иначе. Postgresql очень капризный или ленивый. После MS SQL сервера всё кажется излишним. Очень много вещей надо настраивать самому.

      Приложения написанные на Python, PHP или в моем случае asp.net core. Часто создают одно или несколько подключений при запросе. Но стандартное количество соединений в пуле postgresql по умолчанию = 100. Это давольно оптимальное число для мелких и стредних приложений. А если вы арендуете сервер как я так это еще и выгодно. Но приложение ничего не знает про эту настройку, поэтому пытается создать больше 100. Чтобы обеспечить грамотный пулинг нужно испозоваться пул балансер, например, PgBouncer.

      Лечение

      Нужно установить PbBouncer между приложением и кластером. Оказывается в кластере Postgresql яндекс облака. Уже встроен балансер (PgBouncer).

      Но не понятно как он работает и какой режим пулинга у него по умолчанию. Я думаю что SESSION, который не очень подходит для подобных приложений. К сожалению сейчас у них нет интерфейса для конфигурации этого балансера. Но можно воспользоваться команной сторокой YC (CLI).

      yc managed-postgresql cluster get <CLUSTER NAME> --full
      $ yc managed-postgresql cluster update --help
      
      
      $ yc managed-postgresql cluster update <имя кластера> --connection-pooling-mode <SESSION|TRANSACTION|STATEMENT>

      Установите pooling mode в TRANSACTION

      Более подромные настройки можно глянуть тут https://cloud.yandex.ru/docs/managed-postgresql/api-ref/Cluster/

      Вообще эта настройка у яндекс кластера просто дурацкая. Если есть балансер соединений, то почему я не могу увеличить соединения к PgBouncer-у? Это же блин PgBouncer в этом его суть!

      Настройка Pooling Mode в TRANSACTION в Cloud.Yandex не помогло!

      Тем не менее данная настройка не помогла. Как я уже писал встроенный PgBouncer в яндекс кластере не работает как PgBouncer т.е. вы не можете увелисить соединения.

      У меня часто вылетало сообщение » The connection pool has been exhausted». Очевидно, что где-то утечкая соединений. Однако как найти. Для начала я попробовал посмотреть все ли соединнеия закрываються. В объекту соединения был добавлен дилегат.

          private static NpgsqlConnection CreateConnection(string connectionString, ILogService logService)
          {
              var connection = new NpgsqlConnection(connectionString);
      
              connection.StateChange += (sender, args) =>
              {
                  logService.Info($"ConnectionState: {args.OriginalState}. 
                      Current time: {DateTime.Now.ToShortTimeString()}");
              };
      
              return connection;
          }

      У данного подхода есть минус. Не понятно ID соединения, которое было открыто и закрыто. В общем данный подход скорее всего может помочь, но мне не очень понравился. Я не заметил соединение, которое вызывало утечку.

      Как залогировать PostgreSQL через Npgsql.EntityFrameworkCore.PostgreSQL?

      В конце концо я решил залогировать все что происходи с соединением моего кластера PostgreSQL. Чтобы залогировать все события Npgsql.EntityFrameworkCore.PostgreSQL можно добавить свой провайдер.

      Перед тем как вызвать AddDbContext просто добавьте свой провайдер.

      Npgsql.Logging.NpgsqlLogManager.Provider = new CustomNpgSqlLoggingProvider(logService);
              public static IServiceCollection AddCustomContext
                  (this IServiceCollection services, IConfiguration configuration, bool useCluster = true)
              {
                  var provider = services.BuildServiceProvider();
                  var logService = provider.GetService<ILogService>();
      
                  Npgsql.Logging.NpgsqlLogManager.Provider = new CustomNpgSqlLoggingProvider(logService); // На провайдер
      
                      var connectionString = configuration.GetConnectionString("DbCustomCluster");
                      logService.Info($"Use DbCustomCluster connection: ${connectionString.Substring(0, 19)}");
      
                      services.AddDbContext<DbCustomContext>(options =>
                          options.UseNpgsql(CreateConnection(connectionString, logService), builder =>
                          {
                              builder.EnableRetryOnFailure(3, TimeSpan.FromSeconds(10), new[] { "EnableRetryOnFailure" });
                              builder.RemoteCertificateValidationCallback(ValidateServerCertificate);
                              builder.ProvideClientCertificatesCallback(ProvideClientCertificates);
                          }));
      
                  return services;
              }

      Реализация провайдер, CustomNpgSqlLoggingProvider.cs

      Ссылка на документацию

      /// <summary>
      /// Source documentation https://www.npgsql.org/doc/logging.html
      /// </summary>
      public class CustomNpgSqlLoggingProvider : INpgsqlLoggingProvider
      {
          private readonly ILogService _logService;
      
          public NpgSqlLoggingProvider(ILogService logService)
          {
              this._logService = logService;
          }
      
          public NpgsqlLogger CreateLogger(string name)
          {
              return new NpgsqlCustomLogger(_logService);
          }
      }
      
      internal class NpgsqlCustomLogger : NpgsqlLogger
      {
          public ILogService LogService { get; }
      
          public NpgsqlCustomLogger(ILogService logService)
          {
              this.LogService = logService;
          }
      
          public override bool IsEnabled(NpgsqlLogLevel level)
          {
              // Тут можно указать какой уровень логов показывать. У меня для простоты все логи.
              return true; // all levels
          }
      
          public override void Log(NpgsqlLogLevel level, int connectorId, string msg, Exception exception = null)
          {
              // Я логирую только открытие, сам запрос и закрытие. Выглядит это так
              // Level: Debug, connectorId: 1237043804, msg: Connection opened
              // Level: Debug, connectorId: 1237043804, msg: Executing statement(s):\n UPDATE "Item..
              // Level: Debug, connectorId: 1237043804, msg: Connection closed
      
              if (msg.Contains("Connection opened") || msg.StartsWith("Executing statement") || msg.Contains("Connection closed"))
              {
                  this.LogService
                      .Debug($"POSTGRESQL Level: {level}, connectorId: {connectorId}, msg: {msg.Substring(0, Math.Min(msg.Length, 250))}", exception);
              }
          }
      }

      В общем после долгого иследования я наконец нашел утечку. Это дейтсвительно было соединение, которое не закрывалось. Теперь я буду осторожнее писать запросы.

      Добавление модели в Razor Pages

      https://docs.microsoft.com/ru-ru/aspnet/core/tutorials/razor-pages/model?view=aspnetcore-3.0&tabs=visual-studio

      В этой статье за основу я возьму пример, который описывает сама Майкрософт.

      Довайте добавим модель в Razor Pages. В этом примере мы будем работать со списком фильмов. База данных на основе ORM (в нашем случае Entity Framework core)

      Используя Visual Studio 2019

      Нажимите правой кнопкой по солюшену > Add > New Folder. Имя папки Models.

      Как добавить папку в VisualStudio

      Далее тоже самое, но с папкой. Нажмите правой кнопкой по папке Models.  Выберите Add > Class. Имя класса задайте как Movie.

      Дабавьте свойства в класс Movie:

      using System;
      using System.ComponentModel.DataAnnotations;
      
      namespace RazorPagesMovie.Models
      {
          public class Movie
          {
              public int ID { get; set; }
              public string Title { get; set; }
      
              [DataType(DataType.Date)]
              public DateTime ReleaseDate { get; set; }
              public string Genre { get; set; }
              public decimal Price { get; set; }
          }
      }
      
      

      Свойство ID обязательное. Оно требуется для нашей ORM. То есть когда ORM будет создавать таблицы на основе наших классов. Она будет проверять наличие ID или MovieId (в качестве альтернативы). Если ORM найдет одно из этих свойств, то создаст таблицу с первичным ключом ID или MovieId.

      Атрибут [DataType(DataType.Date)] необязательный. В нашем примере он просто добавит html поле с типом дата. Т.е. создаст поле <input type=»date» />

      Тип date для элемента input

      Скафолдинг стринцы (page scaffold)

      Нам надо добавить файл представления и файл класса. Но, чтобы не делать этого вручную VisualStudio умеет добавлять нужную инфраструктуру автоматически. Данный процесс можно назвать скофолдингом.

      Чтобы воспользоваться скафолдингов в VS. Добавьте папку Pages и Movies внутри. Далее нажмем правой кнопкой Add -> New Scaffolded item как на картинке.

      Razor Pages - Скафолдинг стринцы

      Как добавить CRUD действия на основе нашей модели.

      После комадны New Scaffolded item добавим CRUD действия на основе нашей модели. (CRUD — Это основные действия create, read, update, delete)

      Как добавить CRUD действия в Razor Pages.

      Вы модальном окте «Add razor pages using entity framework (CRUD)». Выберите следуюшие позиции из списков.

      • Model class — укажите свой класс модели (Movie).
      • Date context class — нажмите на плюсик и укажите контекст RazorPagesMovies.Models.RazorPagesMovieContext (VS автоматически добавит нужный класс контекста с записимостями)
      Модальное окно "Add razor pages using entity framework (CRUD)"

      VisualStudio автоматически сгенерирует нам файлы.

      • Pages/Movies: Create, Delete, Details, Edit, and Index.
      • Data/RazorPagesMovieContext.cs

      Razor Pages

      Введение в Razor Pages

      Майкрософт предложила нам альтернативный шаблон разработки веб-приложений. Еще с версии asp.net core 2 появились рейзор страницы (Razor Pages). В asp.net core 3.0 Razor Pages останутся.

      Для удобной разрабоки используйте Visual Studio 2019. Эта среда разработки имеет встроенные шаблоны. Помогает добовлять Razor страницы без лишних действий.

      Минусы и плюсы рейзор страниц (Razor Pages)

      Из плюсов:

      • Разработка чуть более строгая. Т.е. на каждое действие нам буквально нужно добавить страницу. Например, хочешь отобразить позиции добавь страницу list, хочешь добавить новую позицию создай страницу create, обновить update, удалить delete. И т.д.
      Обратите внимание на структуру проекта. На каждое действие у нас отдельная стриница и код.

      На картинке вы видите структуру проекта. Под каждое действие свои файлы. Вы подумаете, что тоже самое было и в MVC, но дело в том, что в коде у нас меньше свободы. В коде вы обращаемся только по глаголу запроса. Например POST, GET и т.п.

      Конечно, данное «ограничение» мы можем обойти. Но это условное ограничение полезно, в будущем, когда проект вырастит это упростит понимание проекта.

      Из Минусов:

      Из минусов я как раз таки и отметил это ограничение которое описано выше. Всё нужно делать с умом. Но к сожалению, чтобы понимать где что использовать с умом надо знать разные технологии для сравнения.

      docs.microsoft.com /ru-ru/aspnet/core/razor-pages/?view=aspnetcore-2.2&tabs=visual-studio

      Asp.net core как подключиться к кластеру PostgreSql (Cloud.Yandex)

      И так друзья, у вас приложение asp.net core 2, 3 на линуксе (Linux) и вы хотите подключиться к кластеру Postgresql на базе Cloud.Yandex. Я как и многие из нас не люблю возиться с настройками среды, поэтому поспешил глянуть инструкцию в яндекс облаке. Но эти придурки из яндекса (да да любить их незачто :-)) не оставили инструкции подключения для нашего asp.net core.

      Я им даже писал в поддержку мол «ваш клиент, может расскажите как подключиться по бырому?». На что мне ответили — «спасибо», а потом —«ваша заявка выполнена». Но инструкции так и не появилось. Просто уроды что можно сказать.

      нет инструкции подключения к postgresql кластеру от яндекс облако

      Итак как подключиться к кластеру postgresql используя asp.net core и Ubuntu?

      Для начала установите на своем сервер сертификат. Выполните следующую команду.

      mkdir -p ~/.postgresql && \ wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O ~/.postgresql/root.crt && \ chmod 0600 ~/.postgresql/root.crt

      Данная команда добавит в папку /home/username/ .postgresql сертификат root.crt

      Для работы с базой Postgresql я использую библиотеку «Npgsql.EntityFrameworkCore.PostgreSQL«, которая заточена под ORM Entity Framework.

      Моя строка соединения. Для читабельности добавил переносы. Подробнее про строку соединения можно почитать тут

      Host=rc4c-blablablablabla.mdb.yandexcloud.net;Port=6432;
      SSLMode=Require; //
      TrustServerCertificate=true;
      Database=YOUR_DB_NAME;
      Username=YOUR_DB_USERNAME;
      Password=*******;
      Pooling=true;

      Чтобы соединение работало надо в option builder-е проставить делегаты. RemoteCertificateValidationCallback и ProvideClientCertificatesCallback

      public async Task<ActionResult<bool>> CanConnect()
      {
                  const string host = "rc4c-blablablablabla.mdb.yandexcloud.net";
                  var connection =
                      $"Host={host};Port=6432;SSLMode=Require;TrustServerCertificate=true;Database=YOUR_DB_NAME;Username=YOUR_DB_USERNAME;Password=*******;Pooling=true;";
                  var optionsBuilder = new DbContextOptionsBuilder<DbAppContext>();
                  optionsBuilder.UseNpgsql(connection, builder =>
                  {
                      builder.RemoteCertificateValidationCallback((s, c, ch, sslPolicyErrors) =>
                      {
                          if (sslPolicyErrors == SslPolicyErrors.None)
                          {
                              return true;
                          }
                          _logService.Error($@"Certificate error: {sslPolicyErrors}, 
                          Do not allow this client to communicate with unauthenticated servers");
                          return false;
                      });
                      builder.ProvideClientCertificatesCallback(clientCerts =>
                      {
                          var clientCertPath = "/home/username/.postgresql/root.crt";
                          // To avoid permission ex run: "sudo chmod -R 777 /home/username/.postgresql/root.crt"
                          var cert = new X509Certificate2(clientCertPath);
                          clientCerts.Add(cert);
                      });
                  });
                  using (var ctx = new DbAppContext(optionsBuilder.Options))
                  {
                      return await ctx.Database.CanConnectAsync();
                  }
        }

      Error:2006D002:BIO routines:BIO_new_file:system

      В процессе настройки у меня вылетала вот такая ошибка error:2006D002:BIO routines:BIO_new_file:system. Оказалось для файла сертификата, который используется в классе X509Certificate2 не было прав на чтение. Пока пофиксил данную проблему просто дав права файлу на сервере.

      sudo chmod -R 777 /home/username/.postgresql/root.crt

      Signalr

      Введение в ASP.NET Core SignalR

      ASP.NET Core SignalR — это библиотека с открытым исходным кодом. Которая позволяет создавать приложения в реальном времени по настоящему. Т.е. не по принципу запрос-ответ, а независимо, когда сервер решает сам когда ответь и не ждет клиента. Например сервер- сервер-клиент или клиент-сервер-сервер. Нет очередности два актора общаются независимо.

      Такое поведение похоже на толстый клиент у которого нет сервер и который работает на нашем пк без подключения к интернету. Поэтому signalR насколько перспективная технологий. Сайт работающий с использованием сигналР я бы назвал web 3.0.

      Почему не везде используют SignalR

      Когда я первый раз понял мощь технологии. Я подумал, а почему сайты не используют signalr? Ведь это так круто, всё в реальном времени. Простой ответ — цена. К сожалению, для рядовых сайтов такая технология будет дорогой. Например максимальное число соединений в теории 65534, для Windows по умолчанию стоит 5000 свободных соединений. Например, если посещаемость нашего сайта 1 млн уникальных пользователей в день, то нам нужно арендовать кластер из 15.5 серверов или виртуальных машин. С другой стороны если ваша посещаемость 1 млн вы уже успешный стартап и скорее всего можете позволить себе 15 виртуальных машин 🙂

      Где использовать SignalR

      100% необходимость использовать SignalR нужна там где скорость визуализации данных критична. Например ПО брокера на бирже. Или АСУ ТП (Автоматизированная система управления технологическим процессом), чаты и т.п. Вообще я бы не исключал данную технологию и для простых небольших сайтов у нас же 65 тыс соединений это всё равно неплохо.

      Через SignalR можно передавать файлы. В одном из проектов мы например передавали звук (в виде байтов) каждые 100 мс небольшими порциями. Собственно байтами можно передавать что угодно в том числе и файлы (но для этого лучше использовать обычный POST запрос, браузер также передаст файл частями на сервер).

      Зачем Использовать .Net Signalr если есть WebSocket

      Signalr работает с android, iOS, можно использовать в консольных приложениях, десктопных приложениям и в WPF. Во вторых Signalr это обертка над веб-сокетом, поэтому если ваш браузер не поддерживает веб-сокеты или пользователь отключил его, то singlar все равно будет имитировать работу в реальном времени через механизм передачи данных Long Polling.

      Какие способы передачи данных в реальном времени поддерживает Signalr Core.

      • WebSocket — протокол (rfc7118) поддерживающий двухстороннюю передачу данных в реальном времени. Доступен почти по всех современных браузерах. И поддерживается некоторыми приложениями ориентированными на веб.
      • Server-Sent Events — по названию можно сказать что сервер передает данные, а клиент слушает данные с сервера, работает по протоколу http.
      • Long polling — способ когда клиент, например каждые 100 мс. долбит сервер сообщениями. Возможно вы неосознанно реализовывали данный способ. Когда на JavaScript заворачивали функцию в setInterval()
      // Пример Long polling с использованием JQuery
      function fetchdata(){
       $.ajax({
        url: 'fetch_details.php',
        type: 'post',
        success: function(data){
         // Perform operation on return value
         alert(data);
        },
        complete:function(data){
         setTimeout(fetchdata,5000);
        }
       });
      }
      
      $(document).ready(function(){
       setTimeout(fetchdata,5000);
      });

      Поддержка WebSocket в браузерах

      • Microsoft Internet Explorer 8, 9, 10, 11. Modern, Desktop, Mobile
      • Mozilla Firefox: на Windows на Mac ОС
      • Google Chrome: Windows и на Mac ОС
      • Safari: на Windows и на Mac ОС
      • Opera: ОС Windows
      • Android-браузер
      • и некоторые другие, смотрите в табличке ниже.
      Поддержка WebSocket в браузерах

      Что такое .NET Core Worker в ASP.NET Core 3.0

      .NET Core Worker Service заместо Windows Service

      Если нам надо чтобы какая-нибудь программа работала сама по себе длительно время или постоянно. И, например раз в минуту или час совершала какие-либо действия, то вы можете использовать воркер.

      Что такое .NET Core Worker? В .NET Core 3.0 мы познакомимся с новым типом проекта Worker Service. Этот шаблон тесно интегрирован с Виндоус службам или крон в линукс. Своего рода проект заточенный для создания Windows Service (служба виндоус) или демон (daemon) в Linux.

      Создать новый проект на ASP.NET Core 3.0

      Как добавить asp.net worker через консоль.

      Если вы используете консоль или работаете на Linux. То воркер можно добавить через команду

      dotnet new worker
      image

      Как запустить .NET Worker через виндоус службу.

      Для того чтобы наш воркер работал в качестве службы нам нужно реализовать механизм запуска, остановки или перезапуска в операционной системе. В шаблоне проекта это удобно реализовано. Для начала установите пакет через NuGet.

      Microsoft.Extensions.Hosting.WindowsServices 

      image

      В класс Program добавим вызов через метод UseServiceBaseLifetime()

      public class Program
      {
          public static void Main(string[] args)
          {
              CreateHostBuilder(args).Build().Run();
          }
      
          public static IHostBuilder CreateHostBuilder(string[] args) =>
              Host.CreateDefaultBuilder(args)
                  .UseServiceBaseLifetime()
                  .ConfigureServices(services =>
                  {
                      services.AddHostedService<Worker>();
                  });
      }

      Метод UseServiceBaseLifetime() проверяет запущен ли Worker Service как служба или вы запустили его локально в вашей среде работки типа VisualStudio. Т.е. вам не нужно проводить дополнительных монипуляций метод делает их за вас.

      Установка Worker Service

      После того как мы указали метод UseServiceBaseLifetime() мы можем установить наш воркер, в качестве службы. Для начала опубликуйте его. В корне вашего проекта запустите команду.

      dotnet publish -o c:\MyFolder\workerpub

      Чтобы администрировать нашу слушжу используйте утилиту от Макрософт SC

      sc create workertest binPath=c:\MyFolder\workerpub\WorkerTest.exe