При изучении ASP.NET MVC у меня всегда возникал вопрос:
А почему в качестве "веб-морды" нельзя использовать клиента, написанного с помощью синтаксиса Razor?
И на протяжении нескольких лет это было нельзя сделать.
Собственно, а теперь это можно сделать с помощью Blazor Server.
Создадим 3 звенное приложение: клиент- сервер - база данных (на самом деле будет 2 звенное приложение без БД).
В качестве клиента будет выступать Blazor Server, в качестве “бэка” будет выступать ASP.NET Core 3.1.
Для иллюстрации возможностей Blazor Server в качестве веб-клиента, я создам приложения для работы с заметками.
Для начало создам сервер. Для создания сервера буду использовать шаблон “Empty”.
Далее необходимо создать инфраструктуру для работы приложения как Web Api. Для этого я создам директорию /Models. Понятно из названия, что в данной папке будет лежат данные, которые необходимы для приложения.
Web Api
Models
Основным сущностью моего приложения будет класс Note. Данный класс будет представлять из себя заметку.
Сущность заметки не представляет из себя ничего сложного:
id - идентификатор объекта в БД
title - наименования объекта
description - “тело” заметке
Repository
Далее нужно создать механизм для работы с заметками. Для этого я создам репозиторий.
Реализацию самого репозитория можно посмотреть по ссылки.
Controller
Без лишних слов, код контроллера
Здесь следует упомянуть, что при создании контроллера я использовал атрибут Route. Из этого следует, что при создании маршрута не нужно указывать шаблон для маршрута.
Startup
Startup — это настройка сервисов необходимых для работы приложения, настройка самого приложения, и настройка среды. В данном случаи Startup представляет из себя почти базовую настройку приложения.
Но в текущей реализации есть один недостаток. Если я вызову мето api/notes/note/{number}, где number - идентификатор, которого нет, то я получу не красивую ошибку.
Такая подробная информация очень полезная при отладке и её нужно записать в лог, но она абсолютно не нужна для клиента.
Для того, что выводить клиенту нужное сообщение я буду использовать Middleware. Суть промежуточного ПО в этом случаи, что я буду вызывать метод из MVC и ловить исключении, которые возникнуть входе исполнение Action’a.
Microsoft любезно предоставила возможность создавать Middleware как угодно. Единственное ограничение: пользовательский Middleware должен принимать RequestDelegate и иметь метод с сигнатурой:
Вот само промежуточное ПО:
И добавляем наш Middleware в класс Startup.cs:
И как результат я имею красивое сообщения для клиента об ошибке.
Client
Как я писал выше в качестве клиента я буду использовать Blazor Server.
Для начала создадим проект на .NET Core 3.1 по типу Empty.
Первое что надо сделать настроить его для работы с Blazor. Для этого я внесу изменение в класс Startup.
После этого нужно создать директорию Pages. В данной директории будет храниться хост для запуска компонентов Blazor.
Для того, чтобы все компоненты могли использовать одинаковые сборки, без директивы using в начале файла, нужно создать файл _Imports.razor.
После внесенных изменений решение выглядит следующим образом:
Я подготовил проект для старта и запуска Blazor. Далее нужно реализовать сам интерфейс.
Я хочу сделать приложение похожее на изображение ниже:
Слева у меня будет часть, где будут отображаться всё существующее заметки. Справа будет карточка выделенной заметки.
Судя по всему мне нужно реализовать 3 компонента: лист с заметками, компонент самой заметки и карточка заметки.
Компонент заметки — это то, как заметка будет представлена в списки, а карточка заметки — это отображение заметки, когда пользователь "кликает" на неё
Первым делом я создам компонент NoteComponent.razor, который будет отвечать за отображение заметки в списки. Данный компонент будет принимать в качестве параметра объект Note, будет менять стиль при взаимодействии с указателем мыши, и будет выкидывать событие о клике.
Без лишних слов, код:
Далее необходимо реализовать компонент, который отображаем список всё заметок, но для начало нужно реализовать сервис для “общение” с WebApi.
Сервис должен получать данные от WebApi по http, для этого ему нужен HttpClient. Т.к. при Dispose объекта HttpClient не всегда освобождается порт, я воспользуется новой возможностью, который появился в .Net Core, IHttpClientFactory.
У HttpClient нет методов, которые позволили мне отправлять и получать данные от сервера в формате Json. Для того, чтобы получить данные в нужном мне формате мне пришлось бы сначала сделать запрос к сервера, затем считать контент и только потом десериализовать его. А т.к. сервис, в клиенте, будет неоднократно получать и отправлять данные серверу, то придется писать данный код много раз. Чтобы этого не дать я вынес данный код в методы расширения для HttCliet.
Сам сервис, который находится в клиенте:
Как по мне выглядит очень симпатично.
В самом компоненте нет ничего сложного. Если интеренсо то реализацию можно посмотреть тут.
Вот и результат:
Все что осталось для того, что реализовать то, что я хотел изначально, так это карточку заметки.
В карточке нужно отображать свойства объекта такие как: Title, Description. Так же в карточке должна быть возможность управлять конкретной заметки, а именно обновлять или удалять её.
Наконец-то появилась возможность реализовывать Web-клиента на Razor.
Из основных недостатков можно отметить, что общение между сервером (где сидит клиент) и самим представление приходит через html. Если реализовать интерфейс, в котором есть 2^10 строчек в таблице. И при каждом клике добавляется ещё столько же. В этом случаи сервер будет гонять мегабайты html.
Blazor… Сколько шума наделала эта технология. Все хотели посмотреть на вторую попытку Microsoft привнести C# в создания интерфейсов. Не уже ли у них это получилось? Давай разберемся.
Как всегда, я создам приложения, но на этот раз не буду создавать приложения по шаблону Blazor App. На этот раз я создам приложения по типу ASP.NET Core MVC и добавлю в него Blazor Component.
Моё приложения будет эмитирует онлайн кинотеатр.
Первое что надо сделать - это создать новый проект по типу Empty.
При создании проект обратите особое внимания на версию .Net Core. Версия должна быть >= 3.1.x
Далее нужно настроить приложения для работы как с инфраструктурой MVC и Blazor.
Для этого добавим в класс Startup.cs следующее:
Первое, что бросается в глаза (ну лично для меня) так это то, что в версии .Net Core 3.1 изменилось подключения сервисов необходимое для работы MVC.
Теперь вместо
мы имеем
Второе нововведение — это изменение настройки маршрутизации.
В .Net Core 2.x настройка маршрутизации проходила следующим образом:
В новой версии .Net Core настройка маршрута происходит следующим способом:
Как по мне такой вариант инициализации машрутов лучше отображается действительность. По мимо MVC приложения также имеются и другие типы приложения. Например: Razor Pages, Web API и Blazor. Именно, поэтому настройка маршрута через метод, который называется UseMvc(), не логична.
Далее необходимо подготовить основную модель для онлайн кинотеатра, которая будет отображаться реальный фильм:
Так же надо ввести типы имеющихся фильмов. Это можно сделать с помощью MovieType:
Далее нужно реализовать отображения фильмов, которые имеются в кинотеатре. Для этого воспользуемся Blazor Component. Компонент будет принимать модель фильма, отображать имя и описания конкретного фильма ну и с эмитируем постер фильма (для этого я буду отображать “заглушку”). Так же компонент должен реагировать на взаимодействия с мышью, т.е. изменить при наведения мыши.
В итоге получилось так:
Из исходного кода компонента видно, что он не представляет ничего сложного. В данном компоненте происходит обработка таких событий как: onmouseover, onmouseout и onclick.
После написания данного компонента нужно его проверить. Для этого запустим приложения …, и оно не работает. Точнее будет, если я скажу, что компонент, который я написал работает только на первой страницы.
Поиске на этот вопрос не дали ничего. Мне даже пришлось спросить у команды, которая занимается разработкой Blazor.
И после ответа от команды Blazor я понял в чем была моя ошибка. Оказывается, что бы Blazor Component работал во всем приложении нужно указать специальный тэг:
Результат:
После этого я реализовал фильтрацию фильмов по категориям (по типам) и открытие “карточки” фильма. Для этого я создал MovieController.cs, которые имеет следующее методы:
Код реализации репозитория можно посмотреть в GitHub.
У каждого фильма есть свои оценки/комментарии от пользователей. Я хочу реализовать что-то подобное и в моем приложение. Для начало создадим сущность, которая будет отображать комментарии для определенного фильма. Данная сущность показана ниже
Так же я реализую контроллер, который будет возвращать комментарии для определённого фильма и добавлять их. Ниже показать код контроллера CommentController.cs:
Так же нужно реализовать компонент для комментариев. Он должен уметь отображать существующее комментарии к определённому фильму, в карточке которого я сейчас нахожусь, и дать возможность пользователю добавлять новые комментарии. Вот что получилось:
Как видно из кода выше CommentsComponent.razor представляет из себя EditForm и список уже существующих комментариев. EditForm - это стандартный компонент, который представляет из себя надстройку над существующей form из Html.Это надстройка позволяет реагировать на submit только, когда Model прошла валидацию (OnValidSubmit=””) или наоборот, когда модель не прошла валидцию (OnInvalidSubmit=””). Эти методы дают возможность контролировать внешний вид компонента при валидации модели. Но в я использую услвоную нажатие на кнопку без проверки входящей модели.
Так же в секции @code видно два метода, которые взаимодействуют с сервером при помощи HttClient. Первое взаимодействия происходит в методе OnInitializedAsync. В данном методе происходит запрос всех комментариев по данному фильму. Второе взаимодействия происходит в методе OnCreateNewComment. В этом методе я отправляю новый комментарий на сервер.
Ниже можно посмотреть результат того:
Вместо вывода:
Blazor+ASP.NET Core MVC сдружить можно, но у меня возникли некоторые проблемы с этим. Первое с чем я столкнулся это то, что в к версии .NET Core < 3.1.x есть проблема при передачи параметра в компонент, т.е. при таком виде
происходило исключения. Это проблема решается путем обновления .NET Core до версии 3.1.
Вторая не приятная ситуация, с которой пришлось столкнуться — это необходимость указания тэга в мастере страниц, для того чтобы компоненты Blazor работали во всем приложение.
В целом мне очень понравилось комбинировать ASP.NET Core MVC и Blazor Component. Надеюсь, при написание вашего проекта у вас не возникнет таких проблем.
Недавно Microsoft представила .NET Core 3, а вместе с ним и Blazor - новый famework для написания Web-приложений. Прежде всего Blazor рассчитан на Fullstack-программистов, которые работали с ASP.NET MVC или/и ASP.NET Pages, но в отличие от данных Framework’ов в Blazor вся логика изменения UI реализована на всеми любимом C#. Теперь для “оживления” Web-сайта можно минимизировать использование js.
Как удалось реализовать это Microsoft?
Microsoft давно уже хотела “запихнуть” C# в Frontend. Ещё в далеком 2002 года Microsoft представила миру первую реализацию ASP.NET Web Forms. ASP.NET Web Forms была хороша, как и Win Forms, для своего времени. Но ASP.NET Web Forms не прошел проверку временем. Microsoft решила абстрагировать разработчиков Web Forms от самих Web-технологий. Таких как: HTTP, HTML, CS и т.д. В надежде, что миллионы разработчиков дескторных приложений для Windows перейдут на Web-приложения. Но с развитием Web’a ASP.NET Web Forms умер. И основные причины этому:
Тяжеловесное состояние страница - современные Web-приложения требовали динамического контента, и в ASP.NET Web Forms решили эту проблему путем передачи гигантских данных между клиентом и сервером при любом изменение страницы;
Отсутсвие четкого шаблона проектирования - за счет этого тестировать и отлаживать enterprise-приложения становилось тяжело, а иногда и просто не реально;
Ограниченный контроль на HTML версткой - создание пользовательских элементов управления было не из легких задач.
Понимая это Microsoft переосмыслила framework для написания Web-приложений. Первым делом убрали абстракцию, теперь разработчик четко понимал что он разрабатывает Web-приложения, а не десктопное приложения в браузере. Как только убрали абстракцию то появился полный контроль над HTML,CSS и js. И это всё было подано с MVC паттерном. Так появился ASP.NET MVC.
Отличная Web-технология, которая позволяет писать хорошие приложения. ASP.NET MVC включает в себя Razor - движок представлений. Язык разметки Razor позволяет генерировать HTML страницы со вставками C# кода. Но для того, чтобы создать динамически изменяемый контент нужно использовать js.
И совсем недавно Microsoft представила новый framework Blazor. Blazor основан на технологии WebAssembly , которая позволяет запускать код, написанный на любом языке, в браузере. Это происходит за счет интерпретации кода в байт-кода.
Microsoft разрабытвает несколько видов приложений на Blazor:
Blazor Client - это платформа для SPA-приложений, которая позволяет создавать интерактивные клиентские веб-приложения с помощью .NET. Это происходит благодаря загрузке .NET Framework’a и .NET Runtime на клиент (в настоящее время доступна только предварительная версия);
Blazor Server - предоставляет поддержку размещения компонентов Razor на сервере в приложении ASP.NET Core, а обновления пользовательского интерфейса передаются через подключение SignalR
Для того, чтобы пощупать Blazor я создам приложение для ведение дел ToDos. Для начало выберу новый шаблон проекта, который доступен в Visual Studio:
В только что созданном проекте находятся примеры, которые Microsoft показывала на протяжение последнего пол года. Подробно я их не буду рассматривать.
А вот первое, что бросается в глаза это способ изменение настройки маршрутизации для приложения на Blazor в сравнение с настройка маршрутизации для приложения ASP.NET MVC. Для наглядности ниже я размещу код Configure :
Как видно из кода выше в Configure отсутствует настройка route, а вместо неё есть настройка endpoints. Данный подход взять из ASP.NET Pages. А за routing отвечает специальный компонент Router, который находится в App.razor:
Blazor как и Angular основан на компонентах, что упрощает повторное использование одно функционала в разных местах. Для меня, как для человека, который пришел в мир Web’a, из мира разработки десктопных и мобильных приложений на Windows, компонент - это UserControl из WPF или UWP. И с этой ассоциацией мне было легче понять, что такое компонент.
Основной компонент Route располагает (хостится) на обычной razor-странице. Ниже показан код _Host.cshtml:
Единственное, что меня смутило так это, что рендер компонента происходит через HTML-хэлпер. Скорей, всего в дальнейшем Microsoft откажется от такого подхода в сторону tag-хэлперов, и вместо строчки :
Будет строчка:
UPD: На сайту Microsoft можно увидеть текущею реализацию (https://docs.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.1)
JS скрипт _framework/blazor.server.js используется для создания связи между сервером и клиентом через SignalR.
Для того, чтобы манипулировать задачам нужно создать сущность, у которой будет название запланированной задачи и свойство показывающее завершенность. Нижне показан код только что созданной модели:
Так же нужно создать сервис, который будет возвращать список задач, для этого я создам класс ToDoList.cs:
Для того, чтобы избавиться от зависимости реализации буду использовать принцип инверсии зависимостей. Для этого реализую интерфейс IToDoList:
Унаследую данный интерфейс классом ToDoList. И для того, чтобы система понимала экземпляр какого класса использовать при использование интерфейса IToDoList буду использовать встроенный контейнер внедрения зависимостей:
При инициализации данного компонента , метод OnInitializedAsync, происходит загрузка задач с помощью нашего сервиса ToDoList, при этом сервис я указал как абстракцию,а реализацию находит сама платформа:
@injectIToDoListToDoList
Но отображать списка дел можно и с помощью обычных razor-страниц, для этого Blazor не нужен. Для того, чтобы воспользоваться всеми прелестями нового framework’a реализую возможность добавления новых задач. Для этого в компонент помещу input и при нажатие на Enter будет создаваться новая задача.
<inputclass="form-control"text="@title"placeholder="Add a new todo"/>
Создам новое приватное поле в блоке @code title, и укажу его в качестве text для только что созданного элемента input.
Теперь нужно подписаться на событие нажатие кнопки на Enter. И здесь началось самое интересное потому, я не сразу понял как это сделать.
Это казалось мне логичными потому, что любые вставки C# кода в HTML происходят с помощью директивы @. Но это не сработало.
При просмотре документации я обнаружил, что чтобы подписаться на событие нужно использовать следующий синтаксис:
Но мне по прежнему было необходимо отслеживать клавишу, которую нажал пользователь. И здесь мне помог ReSharper. Он подсказал нужное событие. В конечном счете данный функционал выглядит так:
<inputclass="form-control"text="@title"@onkeypress="OnKeyDown"placeholder="Add a new todo"/>
@code{
string title;
public void OnKeyDown(KeyboardEventArgs aegArgs)
{
if (aegArgs.Key == "Enter")
{
ToDoList.AddNewToDo(new ToDoItem { Title = title });
title = string.Empty;
}
}
}
Здесь меня ждал сюрприз. Текст, который вводит пользователь, не сохранялся.
Дело в том, что атрибут text=”@title” задавал значение свойству text элементу input при рендере страницы, но не обеспечивал связь между свойством элемента и полем блока @code.
Нужен был некий механизм привязки…
И да, данный механизм есть в Balzor. После не большего изменение, элемент input выглядит следующим образом:
<inputclass="form-control"@bind="@title"@onkeypress="OnKeyDown"placeholder="Add a new todo"/>
Однако, после тестирование я выяснил, что значение поле title изменяется после потери фокуса на элементе input. Меня такой вариант не устраивал и после не большего пояска я нашел решение. После небольших исправлений код элемента input выглядит следующим образом:
<inputclass="form-control"@bind-value="@title"@bind-value:event="oninput"@onkeypress="OnKeyDown"placeholder="Add a new todo"/>
Результат:
Выглядит не плохо, с учетом того, что вся логика добавление новых задач реализована на C#.
Теперь во мне взыграл интерес: а что ещё я смогу сделать?
Я выделил для себя несколько функций, которые можно добавить к приложению:
Не отображать завершенные задачи;
Показывать общие число отображаемых задач;
Возможность выбора отображения: показывать все задачи или показывать не завершенные задачи.
На мой взгляд внимание заслуживает последний пункт, а реализацию остальных возможностей можно посмотреть на GitHub’e.
Для начало добавлю возможность переключения списка задач, которые будут отображаться. Ниже показан добавленный код:
<selectclass="form-control"@onchange="OnChangeSelect"><optionselected>Choose...</option><optionvalue="true">Not show done tasks</option><optionvalue="false">Show all tasks</option></select>
@code{
bool? showNotDone;
public void OnChangeSelect(ChangeEventArgs args)
{
var isNull = Boolean.TryParse(args.Value.ToString(), out var value);
if (!isNull)
{
showNotDone = null;
return;
}
showNotDone = value;
}
}
Свойства showNotDone будет отвечать за генерацию необходимого фильтра. Сам фильтр будет реализован с помощью метода:
Мне кажется, что Blazor - это отличный framework, для написания интерактивного пользовательского интерфейса. Он сочетает в себе всё лучшее, что есть в мире ASP.NET, но при этом избавляется от недостатков и проблем, которые могли бы возникнуть при использование js.
Я много раз слышал про Docker, про систему виртуализации приложений. И вот пришло время для того, чтобы разобраться в этих терминах.
Docker - это программное обеспечение, которое позволяет “упаковывать” приложение и все его зависимости в контейнер. Готовый блок можно переносить на любую среду по управлению контейнерами.
и какой от этого толк?
Наиболее популярным способом разбития монолитной архитектуры на микросервисы была виртуализация отдельных частей (сервисов) в виртуальных машинах (Hyper-V, VirtualBox).
Решение было хорошим до того момента, когда сервисы не начинали разрастаться. И для нормальной работы требовалось наращивание серверных мощностей.
И тут на помощь приходит Docker.
Он, в отличие от виртуальной машины, работает быстро и не требует много ресурсов. Всё потому, что Docker не “создает” гостевую ОС, а использует систему хоста. Для понимания нужно рассмотреть изображения ниже.
При этом у каждого контейнера изолированная файловая система.
Docker работает только на Unix-системах, но благодаря WSL есть возможность запускать Docker на Windows (так же Docker можно запускать с помощью виртуализации, но с появлением WSL2 в этом нет смысла).
С 2015 года Microsoft меняет свою позицию по отношению к Open Source и другим сторонним технологиям. С появлением .Net Core есть возможность упаковывать веб приложения в контейнер Docker’a.
Для того, чтобы включить поддержку Docker’a при создание приложения.
После чего в тулбаре появляется возможность упаковать приложения в контейнер Docker’a.
После того, как приложения было упаковано его нужно запустить. Существуют различные платформы для этого.
Для того, чтобы можно было протестировать ASP.NET Core приложения (и не только), нужно разбить приложения на логические части. Для этого используются архитектурные паттерны: MVC, MVVM, MVP и т.д. Для приложения, написаного на ASP.NET Core, используется паттерн MVC. Данный паттерн предполагает разделения структуры приложения на Model (Бизнес логика)-Controller (Посредник между БЛ и представлением)-View (Представление). Но чтобы приложение было легко маштабируемое, легко поддерживаемое и легко тестируемое нужно, чтобы уровни паттерна зависили от абстракции.
Но вернемся к теме моего поста. Для того, чтобы приложения написанное на Core можно было легко протестировать будем успользовать следующее практики и Framework’и:
Dependency Inversion Principle;
Dependency Injection;
IoC Container;
Moq framework .
И так, Dependency Inversion - это один из приципов SOLID. Он служит для создания слабосвязанных сущностей. Которых, можно легко модифицировать при минимальных изменениях в коде. Не следует путать Dependency Inversion с Dependency Injection. Это разные вещи.
Далее. Dependency Injection - это внедрения зависимостей. Если Dependency Inversion - это прицип, то Dependency Injection - это одна из реализаций этого приципа.
Существует два вида внедрения зависимостей:
Внедрения через конструктор;
Внедрения через свойства.
Внедрения через свойства можно реализовать через атрибуты [Dependency] в Unity, но как по мне лучше реализовывать через конструктор. Тогда объект становится более безопасным, в случае отказа от IoC Container’a. Так, что в рамках этого поста я буду использовать внедрения через конструктор.
IoC Container - это сущность на которой “завязываются” все узелки, именно этот класс и берет на себя “грех” создания объектов. Так же, как и Dependency Injection, IoC Container - это реализация приципов Dependency Inversion.
IoC Container похож чем том на Абстрактную фабрику, только он берет на себя ещё обязанности нахождения зависимостей для каждого создаваемого объекта.
Для тестирования я буду использовать встроенный в ASP.NET Core IoC Container.
Moq framework - это framework, который позволяет имитировать объект и/или его функциональность. Такой объект называется moq-объект.
Что можно тестировать в ASP.NET Core MVC?
Да всё очень просто. Для начало можно тестировать View, или лучше сказать Frontend, который зачастую представлен в виде приложения. Можно также тестировать Model, бизнес логику, и Controller. Тестирования Frontend’a - это отдельная тема для поста. Сейчас же я буду тестировать Model и Controller.
Для начала надо создать слабо связанную архитектуру приложения.
Первым делом я создам интерфейс для репозитория. Репизиторием его можно назвать условно. Конечно, настоящий репозиторий имеет архитектуру гораздно сложнее:
Мой репозиторий будет возвращать список продуктов.
Теперь нужна релизация для моего репизитория. Релизация будет возвращать список продуктов, для простоты я буду создавать продукты прямо в методе:
publicclassHomeRepository:IHomeRepository{publicList<string>GetProducts(){List<string>products=newList<string>();products.Add("iPhone Xs");products.Add("Pixel 3");products.Add("Surface Pro 6");returnproducts;}}
Теперь я создам модель, которая будет использовать данный репозиторий. Но я не буду создавать просто реализацию, т.к. я хочу показать, что можно (и нужно) использовать слабосвязанные сущности, то я создам интерфейс, который будет представлять мою модель, а потом реализацию.
Для начала код интерфейса:
publicinterfaceIHome{List<string>GetProducts();}
Сама реализация:
publicclassHomeModel:IHome{privatereadonlyIHomeRepository_repository;publicHomeModel(IHomeRepositoryrepository){_repository=repository??thrownewOperationCanceledException("ctor: IHomeRepository can't be a null");}publicList<string>GetProducts(){return_repository.GetProducts();}}
Как видно из кода выше, в модели я исполльзовал принцип DI через конструктор. В итоге я получил слабосвязанную сущность, которая зависит от абстракции IHomeRepository, а не от реализации.
Ну что, осталось внедрить зависимость в контроллер:
publicHomeController(IHomehome){_home=home??thrownewOperationCanceledException("ctor: IHome cant's be a null");}
Осталось зарегистрировать объекты в контейнере. Я как писал выше, я буду использовать встроенный контейнер в ASP.NET Core. Вот сообственно и сама регистрация:
Теперь надо настроить отображения и проверить работоспособность приложения.
Так, как будем отображать тип данных strings, то я не буду заморочиваться с представлением.
Вжух-вжух и я реализовал предсталения
Запущу приложения, для того, чтобы проверить его работоспособность.
Всё работает отлично.
Теперь можно создавать тесты. И так, что я буду тестировать? Я буду тестировать модель и контроллер.
Для начало претестирую модель. Для этого нужно создать проект с тестами. Я выбрал xUnit
Именуют тесты обычно так: НаименованияКлассTest,а методы которые нужно протестировать именуют так: TestНаименованияМетода.
Например: есть HomeModel с методом GetProducts() соответсвенно тесты для неё TestHomeModel с методом TestGetProducts();
Ну так, как у меня всего два метода, которых нужно протестировать, то я создам один класс для тестов и два метода: один для модели, другой для контроллера.
И так, для того, чтобы протестировать модель мне надо создать объект модели. А для этого мне нужно передать в конструтор репозиторий как параметр. Если в качестве параметра передовать репозиторий, который используется на “бою” то отсюда появятся два неприятных обстоятельства: во-первых, если репозиторий зависит от абстракции, которая в свою очередь зависит тоже от абстрации, то мне придется писать кода для создания всей это иерархии и лишь для того, чтобы протестировать один метод. Во-вторых, при такой реализации я не пойму где была ошибка в модели или в репозитории, или в зависимости.
Как раз здесь мне и потребуется Moq framework. Как было написано выше с помощью moq’a можно имитировать объект.
Ок, погнали.
Для начало нужно подключить Moq framework к проекту с тестами. Это делается очень просто с помощью nuget.
Далее я создал в конструторе List, который будет я буду позиционировать как правильные данные:
privateList<string>_products;publicHomeTest(){_products=newList<string>();_products.Add("iPhone Xs");_products.Add("Pixel 3");_products.Add("Surface Pro 6");}
При написаниях настоящих тестов такая реализация не допускается, но как говорится: “для примера сойдет”.
И так, а теперь самое интересно реализация метода для тестирования:
[Fact]publicvoidTestGetProducts(){Mock<IHomeRepository>mockRepository=newMock<IHomeRepository>();mockRepository.Setup(m=>m.GetProducts()).Returns(newList<string>{"iPhone Xs","Pixel 3","Surface Pro 6"});HomeModelhome=newHomeModel(mockRepository.Object);Assert.True(home.GetProducts().GetEnumerator().MoveNext());stringsurface=home.GetProducts()[2];Assert.Equal(_products[2],surface);}
Самое интересное здесь - это то, как я создал репозиторий. Как видно из кода выше, я не создаю реализацию репозитирия. Я создал moq-объект, который в свою очередь имитируюет реализацию. Даже если, не знать как создавать moq-объект из кода выше можно понять это: во-первых, я указал интерфейс, для которого мне нужна реализация, во-вторых, я с имитировал возвращаемый результат.
В итоге, мне это дало то, что я получил тест, который тестирует только предметную область, мою модель. И если данный тест не пройдет, я точно буду знать что ошибка в модели, а не в репозитории или ешё где-нибудь.
Теперь я покажу реализацию метода для тестирования контролера:
[Fact]publicvoidTestIndex(){Mock<IHome>mockModel=newMock<IHome>();mockModel.Setup(m=>m.GetProducts()).Returns(newList<string>{"iPhone Xs","Pixel 3","Surface Pro 6"});HomeControllercontroller=newHomeController(mockModel.Object);varindexResult=controller.Index()asViewResult;stringpixel=((List<string>)indexResult.Model)[1];Assert.Equal(_products[1],pixel);}
Здесь происходит примерно тоже самое, что в предыдущем тесте. Был создан moq-объект для IHome интерфейса. Далее я вытащил модель из контроллера и проверил возвращаемые данные.
Тестирование это неотлемлемая часть написания кода. Оно помогает находить ошибки быстрей и исправлять их дешевле. Но для того, чтобы покрыть тестированием большую часть кода нужно принимать различные приемы, паттерны и framework’и, которые были описаны в данном посте.
А закончить я хочу словами С. Макконнелла “…лучшем способом повышения производительности труда программистов и качества ПО является минимазация времени, затрачиваемого на исправления кода…”.
На конец-то я закончил писать пост про ASP.NET. Всё никак не могу выбрать тему для написания на данной технологии, но после долгих раздумей нашел наконец-то то, что искал. И это SignalR.
И так по порядку.
Во-первых, что такое SignalR.
SignalR - это абстракция над абстракцией, которая позволяет создавать динамический контент с использованием веб-технологий (и не только). Данная технология позволяет удаленно вызывать JS кода на стороне клиента. До того, как я узнал про SignalR я использовал для этой задачи WebSocket’ы. Так в чем главное отличие SignalR’а от Web Socket’ов?
А отличие состоит в том, что Signal может использовать WebSocket’ы как транспорт.
А также SignalR может использовать в качества транспорта:
WebSocket;
EventSource;
Forever Frame;
Ajax long polling.
Да, можно на прямую создавать приложения с ипользованием WebSocket, но … зачем? Если можно перейти на ещё один уровень абстрации и избавиться от всех лишних действий.
Для того, чтобы показать как работает SignalR я создам Чат ha, classic.
Для этого нужно создать проект, как на картинке
Сделаем наш чат немного по серьезней и добавим туда базу для пользователей. Для этого я буду использовать ASP.NET Indentity. Скажу честно не с первого раз у меня получилось “стартануть” Indentity.
Первым делом нужно добавить в проект следующее сборки:
Microsoft.AspNet.Identity.EntityFramework;
Microsoft.AspNet.Identity.OWIN;
Microsoft.Owin.Host.SystemWeb.
Далее нужно обновить Web.config, добавим туда следующее строчку:
Теперь нужно создать две сущестности: первая будет отвечать за авторизацию, а вторая за регистрацию:
publicclassLoginModel{publicstringEmail{get;set;}[Required(ErrorMessage="Поле должно быть задано")]publicstringPassword{get;set;}}
Первая сущность будет называться LoginModel. Данный класс будет содержать всего два свойства. При этом Password должен быть обязательным (как-будто без email’a кто-нибудь сможет зарегистрироваться ^_^).
Вторая сущность будет называться RegisterModel, а этот класс будет содержать уже целых три поля:
publicclassRegisterModel{publicstringEmail{get;set;}publicstringPassword{get;set;}[Compare("Password",ErrorMessage="Пароли не совпадают")]publicstringConfirmPassword{get;set;}}
Для того, чтобы не надо было проверять коректность паролей в ручную я буду использовать атрибут Compare, тем самым делегирую часть работы самой платформе.
Теперь осталось создать два специализированных класса. Первый класс будет отвечать за авторизацию и регистрацию, а второй будет отвечать за антентификацию.
Для этого в папке App_Start создадим класс с именем IndentityConfig.cs
В нем создадим два класса (да-да, я знаю что в одном файле не рекомендуют создавать по несколько классов, но для примера можно и создать).
Первый класс будет называться ApplicationUserManager:
Ну здесь вроде все понятно, а если не понятно то: создаем менеджер пользователей, задаем валидатор для проверки пользователей, задаем валидатор для проверки паролей и задаем найстроки блокировки по умолчанию. У UserValidator и PasswordValidator есть множество настройек, с которыми можно “поиграться” для получения нужного эффекта.
Во-первых, я его сделал partial. Это я сделал, для того, чтобы вынесни логику для настройки классов созданных выше. Логика настройки:
publicpartialclassStartup{publicvoidConfigureAuth(IAppBuilderapp){app.CreatePerOwinContext<ApplicationDbContext>(ApplicationDbContext.Create);app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);app.CreatePerOwinContext<ApplicationSignManager>(ApplicationSignManager.Create);app.UseCookieAuthentication(newCookieAuthenticationOptions{AuthenticationType=DefaultAuthenticationTypes.ApplicationCookie,LoginPath=newPathString("/Account/Login"),Provider=newCookieAuthenticationProvider{// Позволяет приложению проверять метку безопасности при входе пользователя. OnValidateIdentity=SecurityStampValidator.OnValidateIdentity<ApplicationUserManager,ApplicationUser>(validateInterval:TimeSpan.FromMinutes(30),regenerateIdentity:(manager,user)=>user.GenerateUserIdentityAsync(manager))}});app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);}}
Предворительная работа была сделана. Теперь можно созадть и контроллеры. Я создам два контроллера: HomeController, AccountController.
HomeController будет отвечать за отображения главной страницы чата: добавления сообщений и новых пользователей.
А вот работа с AccountController будет по интересней. Для начало создадим поля, которые будут отвечать за управления пользователями: входом и аутентификацией.
Я всегда добавляю фильрт на запрос. Мне кажется так, более понятней за что отвечает данное действия, во всяком случаи что делаеть действия. И у меня просто будет два действия с одним именем и разными фильтрами.
Теперь время пришло, для того, чтобы добавить View-ку.
Здесь ничего особенного. Я буду использовать стандартные Boostrap-компоненты.
Давайте разберемся, что я здесь написал. Для начало я указал в качестве модели для View-ки класс LoginModel(y нас почти все View-ки будут строго типизированные ^_^). Далее создаю форму, которая шлет запрос на контроллер (тот самый POST запрос, для которого я ещё не неписал действия).
Здесь я делаю редирект на другое действия, которое отвечает за регистрацию.
Ну и вот что у нас получилось в итоге:
Теперь давайте посмотрим на действия контроллера, которое проверяет валидность пользователя. Тот самый Login c фильтром POST:
[HttpPost][ValidateAntiForgeryToken]publicasyncTask<ActionResult>Login(LoginModelaccount){if(!ModelState.IsValid){returnView(account);}Inint();varresult=await_signManager.PasswordSignInAsync(account.Email,account.Password,false,false);switch(result){caseSignInStatus.Success:returnRedirect("/Home/Index");default:ModelState.AddModelError("","Не удачная попытка входа");returnView();}}
Что здесь происходит? Я проверяю валидность модели, инцилизурую служубные классы (метод Init) и пытауюсь зайти под пользователем.
Берем наш hub, и подписываеся на методы, которые достпуны для клиента. В данном случаии это добавления новых сообщений и добавления новых пользоватлей.
Этот пост будет обзорным. Здесь не будет ни строчки кода. В этом посте я расскажу как создал свое первое приложение.
Почему мне в голову пришла мысль о написании приложения? Дело в том, что идея контролировать личный бюждет у меня возникла давно. Раньше я обходился таблицей в Excel’e, но потом я сказал себе: “Я же программист, почему я не могу написать приложение для этого?”.
Ну с января этого года начал делать какие-то наброски. В первую очередь выбрал метод, который называю “сверху-вниз”. Идея данного метода заключается в следующем: сперва рисуешь интерфейс, а затем пишешь логику.
В первых набросках не придерживался никакого Fluent Design’a потому, что хотел чтобы приложение запускаскалось на телефоне (да, да я использую Windows 10 Mobile), а последняя версия Windows на телефонах не поддерживает данную концепцию дизайна.
Но всё изменилось когда был подключен EF для хранения БД. Дело в том, что Microsoft активно развивает кроссплатформенный .Net Core, и , соответственно, есть EF 6 и EF Core. Я не хотел выбирать EF 6 потому, что в скором будущем остановиться поддержка данного framework’a, и так активно он не будет развиваться как EF Core.
После подключения EF Core приложения не запустилось бы на телефоне. Поэтому было принято решения переработать дизайн в соответсвии с Fluent Design.И вот что получилось.
Но при написании приложения у меня возникла интересная ситуация. Подгружать все элементы на страницу “Затраты” нерационально, поэтому я подгружаю первые десять, а если пользователю покажется, что первой страницы ему малова-то то он в любой момент может подгрузить следующею страницу нажам на кнопку “Refresh”. Для этой ситуации было решено использовать контрол RefreshContainer. Но меня ждало разочирование потому, что в минимальной версии (Windows 10 1709), которая была выбрана, данного контейнера нет. Но при этом в версии Windows 10 1703 данный контрол есть o_0. И тут я осознал всю бренность существования. Пришлось увеличить минимальную версию до 1803. Но на этом сюрпризы не закончились. После обновления минимальной версии и обновилось и UWP SDK. Соответсвено, обновились и некоторые элементы.
И после очердного ребилда я заметил, что у меня две стрелки назад WHF?. И тут сразу вспомнил о чем говорилось на Build 2018 и убрал одну из стрелок назад. Это связано с тем, что в гредущем обновлении добавится новая функция “Sets”, которая займет заголовок окна и использовать кнопку назад в заголовке станет невозможно. И Microsoft обновила контрол NavigationView добавив туда программную кнопку назад. Что в моем случае (т.к. я использовал данный контрол) сыграла на руку: во-первых, это потребывало минимальных изменений в коде, во-вторых, моё приложение готово к гредущему обновлению.
Исходный код моего приложения лежит на GitHub’e. Если есть идеи, то you are welcome!
После релиза UWP Microsoft показала как должны выглядить современные приложения на Windows 10. И так вопрос: что должно быть у приложения, чтобы оно соответствовало универсальной платформе? Канечно же адаптивный пользовательский интерфейс, так как UWP может запускаться на различных устройствах, с различной диагональю экрана (или без).
Например, для рассмотрения я взял стандартный клиент для почты. У него имеется два состояния (на самом деле у данного клиента есть три состояния, но для моего примера важны только два):
“Narrow”
“Wide”
При этом, в разных состояниях меняется и UXмастер UI/UX:
“Narrow”
“Wide”
Покопаясь в интернете я нашел, что-то… А точнее описание такого паттерна поведения GUI, который называется Master/details pattern.
Т.е. есть уже готовое решение, которое можно использовать для написания приложения на UWP. Нужно всего лишь добавить шаблон, пару стилей и “вуаля” - всё готово. Но не всё так просто, как казалось на первый взгляд.
Первом делом я расмотрел то, что предлагает Microsoft. А именно Master/details sample.
Дальше решение от Microsoft. Слабонервных попрошу уйти.
Давайте поставим все точки над i. Что нужно делать-то?
Взять Grid. Разбить этот Grid на несколько колонок. В нем определить VisualStateManager, которые будут реагировать на изменения размера. И в случае, когда ширина Grid’a будет меньше определенного размера, “захлопнуть” колонку с DetailView и показать DetailViewPage.
Почему это решение нельзя назвать полноценным MasterDetailView?
Во-первых, ContentPresenter’y был задан определенный шаблон, и из-за этого в DetailView можно поместить только элемент из ListView. Следовательно, можно забыть об использовании CommandBar’a в MasterView.
Во-вторых, так как ContentPresenter представляет из себя обычный контейнер для отображения содержимого ListView, то можно забыть о навигации внутри DetailView (как это реализовано в стандартном почтовом клиенте).
Microsoft как-бы сказали:”Вот так вот должно выглядить приложение. Вот такой паттерн нужно использовать. Но готового контрола для реализации этого паттерна в приложениях у нас нет, но вы можете его сделать сами! Никто Вам не мешает.”
Ок. Я так и сделаю.
Что мне нужно от full MasterDetailView?
Во-первых, у данного представления должно быть два рабочих состояния, как у стандартного клиента для почты.
Во-вторых, в данном представлении должны отсутсвовать недочеты, которые есть в примере от Microsoft.
Может кто-нибудь уже написал что-то похожее? Откроем исходники Unigramпотомучто!.
Поведения приложения Unigram меня устраевает, но их решения слишком неоправданно сложное (кому интересно, может посмотреть в исходники). Разработчики данного приложения используют NavigationService, что для приложения с нестрогой MVVM архитектурой необязательно.
Ну что же? Раз решил давай напиши.
Мне нужен контрол, состоящий из двух частей: MasterView и DetailView. При этом в MastrView’e должен находится не только ListView, а любой элемент, который разработчик захочет. А DatailView должен содержать Frame, чтобы можно осуществлять навигацию по страницам.
Для начала возьмем и создадим класс, который назавем, ну например, MasterDetailView и который наследуется от ContentControl. Почему наш класс должен наследоваться от ContentControl? В MasterDetailView у нас будет ContentPresenter. А как говорил один легендарный .net разработчик: “Лучший контейнер для ContentPresenter’a - это ContentControl”.
Для чего нам нужны обработчики для событий Loaded и Unloaded? В этих обработчиках я буду подписываться (и отписываться) на события кнопки Back, примерно так.
В методе OnBackRequested() я буду переходить на предыдущую страницу.
Что нам ещё нужно? Мне нужно, чтобы внешний пользователь,тот кто использует мой контрол мог узнать текущие состоние. Для этого создадим обычный enum, который характеризует эти состояния.
А в классе MasterDetailView будет свойство CurrentState, которое показывает текущее состояние.
Теперь нам надо менять свойства CurrentState при изменении в VisualStateGroup. Для этого переопределим метод
OnApplyTemplate() и я напишу в нем следующее.
Здесь я подписываюсь на событие CurrentStateChanged у VisualStateGroup. Данное событие возникает, когда происходит изменение состония.
В методе OnCurrentStateChanged() меняю значение у свойства CurrentState. Его реализация
Далее нужно реализовать метод, который бы изменял видимость у _masterPserenter и _detailPresenter.
В нем не будет ничего сложного. Назову-ка я его UpdateView().
Теперь пожалуй один из главных моментов, так как у меня не используется NavigationService, то моему контролу нужно как-то переходить на другие страницы.
Для это я создам в классе MasterDetailView метод Navigate(), который принимал был тип страницы и параметры, которые необходимы для перехода.
Давай-те посмотрим на шаблон по внимательней. Что я изменил?
Во-первых, в качестве MasterView выступает ContentPresenter. А это означает, что в нем можно поместить всё что угодно.
Во-вторых, в качестве DetailView выступает Frame (да здравствует навигация без NavigationService).
Вот и наступил тот долгожданный день,когда ко мне пришел Resberry Pi 3.
Заказывал я его из Китая и через пару недель он добрался до меня. Зачем я его заказывал?
Всё хотел посмотреть что за зверь этот Windows 10 IoT Core. После того, как Microsoft представила UWP и объявила о том, что приложения написанные на универсальной платформе могут запускаться на разных устройствах, в том числе и на IoT. Мне стало интересно и я захотел посмотреть и разобраться в том, как с этим работать.
Первым делом я зашел на портал Microsoft, чтобы посмотреть с чего мне начать. Microsoft предлагали купить устройства, которые подерживали Windows 10 IoT Core. Так как этот пункт меня не интересовал, я его пропустил. Далее предлагали настроить устройства, но для начала мне нужна была SD-карта (SD card-based devices). Немножко покопаясь я нашел карты, которые рекомендовали Microsoft.
Дело оставалось за малым, зашел в Ситилнк, заказал рекомендованную почти флешку (Рекомендованная: Samsung 32GB EVO Class 10 Micro SDHC. Заказал: Samsung 32GB EVO Pluse Class 10 Micro SDHC).
Я, с большой надеждой, начал записывать образ OC’и на флешку.
Всё было хорошо, до появления сообщения об ошибке. Как наивный человек, я попробывал ещё 3 раза записать на флешку Windows 10 IoT Core и как итог: я получил только седую бороду.
Делать было нечего нашел старую флешку на 16 Гбайт 4 Class и попробывал с ней…Записалось WTF Microsoft?
После чего я собрал свой Rasberry Pi 3, подключил питание и начал ждать, пока мой rasiot (мой мини пк) отобразиться в IoT Dashboard’e. В инструкциии было сказано, что подключение к локальной сети может занять 15 минут. Это зависит от скрости чтения с флешки, а так как флешка, которую я использовал, была не самого хорошего класса, я подождал 40 минут, но так ничего не добился.
После чего я подумал:” А что если rasiot не может подключится к Wi-Fi?”- и подключил его к локальной сети с помощью кабеля. Через пару секунд rasiot отобразился в Dashboard’e.
Полдела сделано. Мне стало интересно, а как выглядит Windows 10 IoT Core и что с ней можно делать.
Я представил себе какой-нибудь плиточный интерфейс (CShel on all). Ну что же надо зайти на устройтва. Открыл Device Portal. Как ни странно догадаться от меня потребывали логин и пароль администратора. Пароль я вводил при настройке, а вот логин я не вводил. Пришлось немножко потыкаться в IoT Dashboard’e, чтобы найти “Имя пользователя”. Имя по умолчанию Администратор.
Ввожу имя пользователя и пароль - ошибка. Ещё раз - ошибка. И тут я подумал: “Администратор имя по умолчанию для русской локализации, а у меня на rasiot’e , скорей всего, стоит английская локализация. А что если вдруг имя Администратор для rasiot - это Administrator”. И как ни странно я смог залогиниться.
Windows 10 IoT Core для внешнего наблюдателя представляет из себя web-портал.А как же CShell on all?
И так теперь можно запускать UWP приложение на нашем мини пк. Для этого запускаем Visual Studio. Создаем пустой UWP проект “SampleForRasIoT”.
Ничего не будем изменять в бланке по умолчанию. Запустим отладку на “Удаленном компьютере”. При первом запуске отладки на удаленом копмптьютере Visual Studio попросит ввести IP адрес нашего устройства, на котором будет проходит удаленная отладка. Вводим IP rasiot. После того, как успешно был введен IP адрес, можно переходить в “Портал устройства” и посмотреть запущено ли наше приложение.
Как видно приложение работает нормально.
Ну что же пустое приложение на мини пк вещь интересная, но бесполезная. Напишем-ка приложение, которое показывало бы текущую погоду. В качестве сервиса с погодой будем использовать OpenWeatherMap.
Первое что надо сделать - это зарегистрироваться как разработчик. Это нужно для того, чтобы сервис OpenWeatherMap предоставил нам APIID для использования сервиса.
Так как у нас rasiot может только отображать информацию, то реализуем одностраничное приложние, которое бы показывало текущую погоду на день.
Добавим в проект “SampleForRasIoT”, на страницу MainPage.xaml, нужные элементы. Добавим заголовок, который будет отображать геолокацию (Nuzhniy Novgorod Forever), показание с “термометра”, ну и пиктограмму погоды.
Теперь добавим данные на страничку. Для этого подпишемся на события Loaded страницы. Создадим метод. В этом методе создадим httpClient, который будет стучаться до openweathermap и брать от туда данные и задавать элементам на страничке.
Так как данные к нам приходят в json-формате (формат можно выберать из: json, xml и html), то их нужно распарсить. Для этого добавим в проект Newtonsoft.Json через Nuget. Далее нужно преобразовать данные с сервера в модель для того, чтобы ими было легче манипулировать. Так как мне лень писать для response модель, то воспользуемся динамическими возможностями языка C#, т.е. ключивым словом dynamic.
Здесь нужно отметить, что “стучаться” за данными нужно на адрес:
http://api.openweathermap.org/data/2.5/weather
А чтобы получить пиктограмму на другой адрес:
http://openweathermap.org/img/w/
Теперь нужно проверить, что у нас получилось. Для этого запустим приложение.
Конечно хорошо, что приложение показывает гелокацию и погоду, но для настоящего погодного приложения этого маловато. Сервис OpenWeatherMap ограничивает прогоноз погоды одним днем, но это не означает что я не могу вывести всю доступную информацию о погоде на день.
Покапаясь в API openweathermap я понял какие данные можно получить от сервиса. В соответсвие с этим данными и был переработан интерфейс приложения (добавил пару элементов). Также я добавил автообновление данных каждые 10 минут.
Запускаем приложение на rasiot’e и смотрим, что получилось.
Я был сильно удивлен, когда узнал что при создании WPF-проекта нужно будет создавать свой диалоговый сервис. Разработчику доступны диалоги только с основным функционалом: открыть, сохранить и т.д.
Так для чего он вообще нужен-то? Он нужен для отображения модальных окон, отличающихся от стандартных.
Для того, чтобы показать как можно сделать простой диалоговый сервис я создам простое WPF-приложения.
Добавим в решение ещё один проект «Библиотеку классов».
Приступаем к написанию сервиса. Для начало создадим интерфейс в проекте DialogService, который будет является супер типом для ViewModel’ей. Он будет пустой. Я его назову IViewModelModal.
Затем я создаю интерфейс, который будет представлять диалоговый сервис.
Ну всё. Теперь можно пойти и выпить чаю потому, что диалоговый сервис готов.
Осталось дело за малым, показать как он работает. Для этого на главное окно добавим кнопку, которая и будет вызывать диалоговое окно.
Так как диалоговый сервис предполагает использования паттерна MVVM то логичней было бы сделать привязку команды в ViewModel. Для этого создаем простой класс Command, который реализует ICommand.
Далее нужно реализовать окно для диалога. Оно представляет из себя обычное окно, которое содержит TextBox по середине. Для этого создаем ModalViewModel, которая содержит одно текстовое свойства.
Далее реализовываем MainViewModel. Данный класс будет содержать целую одну командищу.
Пожалуй начнем с того, для кого я веду этот блог. Как не странно, я его завел только для себя. Да да, для себя. Мой блог — это мой внутренний монолог, который я буду вести на просторах интернета. Так как я являюсь начинающим программистом, то мой блог будет посвящён программированию на C# и не только. Так же я хочу выражать мысли по поводу изменений в IT сфере и о новых «штучах». Как будет на деле, мы увидим позже.
Почему в качестве основы для своего блога я выбрал wordpress? Всё очень просто. Мне понравилось то, что предлагал wordpress. А он предлагает бесплатное размещение на своих хостах и возможность зарегистрировать домен 3-го уровня.Вот это я и посчитал оптимальным решением для блога.
Да этого я ни разу не работал на wordpress, но решил что для меня это будет не лишним опытом. И вот 17 ноября я зарегистрировал свой блог. С CMS я разобрался довольно быстро и приступил к оформлению блога. Так, как я знал что хочу видеть, то тему для оформления я нашел сразу же. Осталось от редактировать внешний вид и «вуаля» мой блог готов.
Но со временем, я понял что WordPress не совсем меня устраевает. Поэтому я перенс свой блок на GitHub.