Keymatic notes

Description
Записки C#/.Net разработчика
.Net Developer Notes

Чат группы - @dotnet_notebook_chat

По всем вопросам обращаться - @urgonblg
Advertising
We recommend to visit
HAYZON
HAYZON
6,053,581 @hayzonn

لا اله الا الله محمد رسول الله

👤 𝐅𝐨𝐮𝐧𝐝𝐞𝐫: @Tg_Syprion
🗓 ᴀᴅᴠᴇʀᴛɪsɪɴɢ: @SEO_Fam
Мои каналы: @mazzafam

Last updated 3 weeks, 3 days ago

Architec.Ton is a ecosystem on the TON chain with non-custodial wallet, swap, apps catalog and launchpad.

Main app: @architec_ton_bot
Our Chat: @architec_ton
EU Channel: @architecton_eu
Twitter: x.com/architec_ton
Support: @architecton_support

Last updated 2 weeks, 4 days ago

Канал для поиска исполнителей для разных задач и организации мини конкурсов

Last updated 1 month ago

1 year, 3 months ago

PATCH vs PUT#code_tips

Сегодня в очередной раз столкнулся с тем как люди пытаются решить распространённую проблему изобретением велосипеда: как правильно обновлять часть полей объекта через REST API. Рассмотрим ситуацию: у нас есть endpoint PUT /api/documents/{id}, обновляющий документ со множеством полей. И вот в какой-то момент появляется потребность обновлять не весь документ, а только его часть.

Многие решают использовать null в поле как индикатор: обновлять ли поле или оставить его прежним. Итак, код может выглядеть так:

```
var entity = new Document
{
Field1 = apiModel.Field1 ?? entity.Field1,
...
FieldN = apiModel.FieldN ?? entity.FieldN
};

```
Подход имеет свои недостатки (помимо того что выглядит страшно). Иногда null может означать, что поле просто не задано, и API сохранит старое значение, что может вызвать ошибки.

Существует стандартное решение: метод PATCH. Этот HTTP-метод предназначен для частичного обновления объекта. И для него существует особый формат тела запроса, например:

```
[
{ "op": "add", "path": "/field1", "value": ["foo", "bar"] },
{ "op": "replace", "path": "/field2/prop1", "value": 42 },
{ "op": "move", "from": "/field3/c", "path": "/field4/d" }
{ "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]

`` В .NET можно использоватьMicrosoft.AspNetCore.JsonPatch.JsonPatchDocument` для обработки таких запросов, например так:

```
[HttpPatch("{id}")]
public async Task Patch(string id, [FromBody] JsonPatchDocument apiModel)
{
var entity = await Repository.GetByIdAsync(id);

apiModel.ApplyTo(entity, error => { ModelState.AddModelError(error.Operation.path, error.ErrorMessage); }); if (!ModelState.IsValid) return new UnprocessableEntityObjectResult(ModelState); return NoContent();

}

```
Если ваш API-клиент не поддерживает PATCH, то, конечно, можно использовать PUT или POST. Но если возможно, выбирайте PATCH: это будет интуитивно понятно для пользователей вашего API.

IETF Datatracker

RFC 6902: JavaScript Object Notation (JSON) Patch

JSON Patch defines a JSON document structure for expressing a sequence of operations to apply to a JavaScript Object Notation (JSON) document; it is suitable for use with the HTTP PATCH method. The "application/json-patch+json" media type is used to identify…

**PATCH vs PUT**[#code\_tips](?q=%23code_tips)
1 year, 3 months ago
**AiAssistant - Шаг 22. Создание отклика …

AiAssistant - Шаг 22. Создание отклика на вакансию#aiassistant

Отпуск — это хорошо, но чат бот сам себя не напишет (пока!) так что погнали дальше. Сегодня добавим функцию отклика на вакансию, доступную в API, на наш сайт. В идеале мы хотим иметь единый интерфейс для создания "conversations", в том числе откликов на вакансии. Но кастомизация шаблонов "prompts" потребует много времени. Поэтому начнем с простой страницы и API метода для рабочего прототипа, а кастомизацией займемся позже.

Добавим кнопку “Respond to vacancy” на страницу со списком "conversations":

```


mdi-plus
Respond to vacancy

```
Создаем новую страницу resume.vue. На нее добавим форму с полями “vacancy” и “resume” и кнопкой Save:

```

v\-model="vacancy"
filled
counter
rows="8"
label="Vacancy"
hint="Paste the vacancy text here"
:readonly="loading"
/>
...

```
Обработчик для кнопки выглядит аналогично обычным conversations:

```
async add() {
const payload = {
resume: this.resume,
vacancy: this.vacancy
}
await this.$axios.post('api/Conversations/optimize-resume', payload)
.then(() => {
this.$router.push('/conversations')
})
}

```
После запуска приложения заполняйте форму своим резюме и вакансией, чтобы увидеть ответ бота. Качество ответа зависит от содержания резюме, описания вакансии и запросов к ChatGPT. У меня получился приличный отклик после 3 попыток. Его придется подкрутить немного перед отправкой, но это явно быстрее чем если бы я сам его придумывал. Если нужно откликнуться на несколько вакансий, то экономия времени будет еще больше. Эх, мне бы этот инструмент когда нужно было отзываться на 10-15 вакансий в день на Upwork.

Github commit

1 year, 4 months ago

Сохранение состояния контейнера PostgreSQL в Docker#code_tips #docker

Привет, друзья! Если вы когда-либо сталкивались с ситуацией, когда данные из вашего Docker-контейнера для PostgreSQL исчезали после закрытия Visual Studio или перезагрузки системы, у меня для вас хорошие новости. Есть способ сохранять данные между сессиями. Давайте разберём, как это делать.

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

Для наглядности рассмотрим конфигурационный файл нашего приложения Ai Assistant. У нас тут два контейнера: key.aiassistant.api и database.

Сначала определим новый Volume под именем postgres-data:

```
volumes:
key-ai-assistant-data:

```
Теперь давайте подключим этот Volume к нашему контейнеру базы данных:
volumes:

```
- key-ai-assistant-data:/var/lib/postgresql/data

```
Таким образом, весь конфигурационный файл будет выглядеть так:

```
version: '3.4'

services:
key.aiassistant.api:
ports:
- "8000:80"
- "8443:443"
image: ${DOCKER_REGISTRY-}keyaiassistantapi
build:
context: .
dockerfile: Key.AiAssistant.API/Dockerfile
depends_on:
- database

database:
image: "postgres"
restart: always
ports:
- "5432:5432"
environment:
- POSTGRES_NAME=keyAiAssistant
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=mysuperpassword
volumes:
- key-ai-assistant-data:/var/lib/postgresql/data

volumes:
key-ai-assistant-data:

```
Добавив эти конфигурации, данные PostgreSQL будут сохранены на Docker-управляемом томе вашей хост-системы и будут сохраняться при перезапусках и выключениях контейнера. Когда вы запустите контейнер с помощью docker-compose up, он будет использовать существующий том, и ваши данные останутся нетронутыми.

Надеюсь, этот совет поможет вам сделать процесс разработки немного проще и комфортнее!

1 year, 4 months ago
**API модели**Всем привет! Рассмотрим один из …

API моделиВсем привет! Рассмотрим один из ключевых моментов при создании RESTful API — API модели.

Когда мы внедряли функцию редактирования Conversations, в контроллере API был использован UpdateConversationDto. Но если попробовать создать Conversation через Swagger, возникают вопросы: какой id использовать? Тот, что указан в body, или тот, что в route? Какой из них применится, если эти значения различаются?

Решение очевидно: чтобы устранить путаницу, структура body и route должны быть ясными и однозначными. В контексте REST API путь к эндпоинту должен включать id в таком формате: /api/v1/conversations/{id}. Соответственно, логичным будет исключение id из body для предотвращения дублирования.

Поскольку текущий контроллер зависит от UpdateConversationDto для передачи данных обработчику команд, просто исключить id не получится. Создадим UpdateConversationApiModel только с полем Title и применим его в контроллере:

```
public async Task Put([FromBody] UpdateConversationApiModel conversation, int id)
{
var command = new UpdateConversationCommand
{
UpdateConversationDto = new UpdateConversationDto
{
Id = id,
Title = conversation.Title,
}
};
await _mediator.Send(command);
return NoContent();
}

```
Область применения API моделей на этом не ограничивается, еще их можно использовать в следующих случаях:

1️⃣ Адаптация данных: когда требуется возврат или прием данных в формате, отличном от внутреннего представления.

2️⃣ Безопасность: для ограничения доступа к определенным полям.

3️⃣ Дополнительная информация: когда вам нужно предоставить данные, которые не включены в доменную модель.

4️⃣ Специфичная валидация: которая отсутствует в доменной DTO.

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

Github commit

1 year, 4 months ago

Null VS пустые коллекции#code_tips

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

Однако, возвращение null может усложнить ваш код. Во-первых, это приводит к необходимости дополнительных проверок. Во-вторых, рискуете столкнуться с исключением NullReferenceException.

Пример неправильного подхода:

```
// Bad method
public Item[] GetArrayBad()
{
return null;
}
// Usage
public void Run()
{
var items = GetArrayBad();
if (items == null)
DoSomething();
DoOther(items);
}

```
Лучше возвращать пустую коллекцию. Это упрощает чтение и работу с кодом.

Пример правильного подхода:

```
//Good method
public Item[] GetArrayGood()
{
return Array.Empty();
}
// Usage
public void Run()
{
var items = GetArrayGood();
DoSomething(items);
}

```
Помните: предпочтение следует отдавать возврату пустых коллекций вместо null. Это делает ваш код чище и безопаснее.

1 year, 4 months ago
**AiAssistant - Шаг 21. Вывод сообщений …

AiAssistant - Шаг 21. Вывод сообщений беседы#aiassistant

Всех с пятницей! На сегодняшний день у нас на очереди визуализация сообщений в наших Conversations. Для начала нам надо загрузить сообщения на странице _conversationId.vue:

```
async asyncData (context) {

const messages = await context.app.$axios.$get(api/Conversations/${context.params.conversationId}/Messages)
return {
conversation: …,
prompts,
messages
}
},

```
После загрузки мы передаем сообщения как prop в компонент EditConversation. Но перед тем, как их отображать, необходима небольшая предобработка:

```
messages: this.messages?.map((m) => {
return {
from: m.fromAi ? 'Ai Assistant' : 'You',
text: this.splitText(m.text.trim()),
time: new Date(m.createdDate).toLocaleString(),
color: m.fromAi ? 'green' : 'deep-purple lighten-1'
}
})

```
Итак, сообщения обработаны, теперь нужно их правильно отобразить. Я решил использовать компонент timeline из библиотеки Vuetify:

```




v\-for="(message, index) in conversation.messages"
:key="index"
:color="message.color"
small
>



{{ message.from }} @{{ message.time }}

{{ message.text }}





```
Тадам! Теперь у нас полностью рабочий инструмент для создания, просмотра и редактирования ежедневных полезных подсказок от Chat GPT!

Github commit

1 year, 4 months ago
**AiAssistant - Шаг 20. Cоздание новых …

AiAssistant - Шаг 20. Cоздание новых Conversations#aiassistant

Мы уже научились редактировать Conversations. Настало время добавить функционал создания новых.
1️⃣ Кнопка для новой Conversation. Сначала давайте добавим кнопку для создания новой Conversation в верхней части списка:

```


mdi-plus
Start new conversation

`` ***2️⃣*** **Страница создания Conversation**. Как вы видите, кнопка перенаправляет нас на страницу/conversations/new. Эта страница будет иметь несколько отличий от страницы редактирования: • Нет загрузки Conversation по id и нет необходимости передавать эти данные в компонент EditConversation • Есть загрузка списка Prompts и передача их в компонент EditConversation Шаблон страницы получается совсем простой:`3️⃣ Компонент EditConversation. Добавляем в компонент новый контрол со списком Prompts:

```
v\-model="conversation.prompt"
v\-if="!id"
:items="promptItems"
:readonly="loading"
item\-value="id"
item\-text="title"
label="Prompt"
clearable
return\-object
single\-line>

```
А в коде сохранения добавляем метод add(), который отличается от update HTTP методом PUT, отсутствием {id} в URL и моделью:

```
async add() {
var payload = {
title: this.conversation.title,
promptId: this.conversation.prompt?.id
};
await this.$axios.post('api/Conversations', payload)
.then((res) => {
this.$router.push("/conversations");
});
}

```
Теперь у нас есть все необходимое, чтобы создавать новые Conversations! Однако перед тем, как закончить с ежедневными подсказками, у нас есть еще одна задача: отобразить ответ от ChatGPT. Отличная задача для пятницы!

Github commit

1 year, 4 months ago
**AiAssistant - Шаг 19. Создаем форму …

AiAssistant - Шаг 19. Создаем форму для редактирования Conversation#aiassistant

Всем привет! Сегодня мы добавим возможность изменения Conversations через форму редактирования.

1️⃣ Создание компонентаСоздаем новый файл EditConversation.vue в директории /components/conversations и добавляем в шаблон поле ввода для title и кнопку сохранения:

```
:rules="[required]"
:readonly="loading"
label="Title">

Submit

```
2️⃣ Настройка данных для компонентаВ

1 year, 4 months ago

AiAssistant - Шаг 18. Интеграция API с frontend приложением#aiassistant

Привет всем! Сегодня мы будем интегрировать наше API с приложением на VueJs. Мы уже создали компонент для отображения списка Conversations. Теперь наша задача — загрузить данные для этого компонента прямо из API, а не использовать заранее заданные (хардкоднутые) данные.

1️⃣ Настройка компонента списка: Открываем файл conversations.vue. Удаляем метод data() из секции скриптов и заменяем его на asyncData() с таким содержимым:

```
asyncData(context) {
return context.app.$axios.$get('api/Conversations')
.then(response => {
return {
conversations: response.map(c => {
return {
id: c.id,
title: c.title,
updatedDate: new Date(c.updatedDate),
promptName: c.promptName
};
})
}
});
}

```
Для обращения к API мы используем модуль @nuxtjs/axios(выбрали его при установке), форматируем ответ API если надо (дату преобразуем из строки).

2️⃣ Настройка карточки Conversation: В компоненте ConversationListItem.vue изменяем тип данных для поля updatedDate на Date и указываем, что хотите отображать дату в локальном формате:

```
div class="text-caption">{{updatedDate.toLocaleString()}}

`` ***3️⃣*** **Изменение API**: Наш API пока не возвращает полейpromptNameиupdatedDate`. Добавим их в ConversationListDto. В репозитории создаем метод GetAllWithDetail для получения связанной сущности Prompt:

```
public async Task> GetAllWithDetail() {
return await DbContext.Conversations
.Include(t => t.Prompt)
.ToListAsync();
}

`` ***4️⃣*** Обновляем профиль AutoMapper для преобразованияConversation.Prompt.NameвConversationListDto.PromptName` и готово!

```
CreateMap()
.ForMember(dest => dest.PromptName, e => e.MapFrom(s => s.Prompt == null ? null : s.Prompt.Title));

```
Отлично, теперь мы можем видеть все наши Conversations на странице VueJs приложения.

Github commit

GitHub

Added API to load list of conversations · keymatic/AiAssistant@e3b8327

Contribute to keymatic/AiAssistant development by creating an account on GitHub.

1 year, 4 months ago
**AiAssistant - Шаг 17. Добавляем компонент …

AiAssistant - Шаг 17. Добавляем компонент со списком Conversations#aiassistant

Привет! Начнем создавать интерфейс нашего приложения. Для начала выведем список уже созданных Conversations. Выводить будем в виде карточек с заголовком Conversation, датой и названием Prompt.

Т. к. при создании приложения мы выбрали Vuetify, то идём сюда и смотрим доступные тэги и стили для оформления наших карточек. В папке components/conversations создаем файл ConversationListItem.vue. В тэге

We recommend to visit
HAYZON
HAYZON
6,053,581 @hayzonn

لا اله الا الله محمد رسول الله

👤 𝐅𝐨𝐮𝐧𝐝𝐞𝐫: @Tg_Syprion
🗓 ᴀᴅᴠᴇʀᴛɪsɪɴɢ: @SEO_Fam
Мои каналы: @mazzafam

Last updated 3 weeks, 3 days ago

Architec.Ton is a ecosystem on the TON chain with non-custodial wallet, swap, apps catalog and launchpad.

Main app: @architec_ton_bot
Our Chat: @architec_ton
EU Channel: @architecton_eu
Twitter: x.com/architec_ton
Support: @architecton_support

Last updated 2 weeks, 4 days ago

Канал для поиска исполнителей для разных задач и организации мини конкурсов

Last updated 1 month ago