Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин
launchSettings.json,
- в переменной среды ASPNETCORE_URLS,
- в аргументе командной строки --urls,
- в конфигурации хоста по ключу urls,
- с помощью метода расширения UseUrls.
Привязка HTTP не изменилась.
Описание критического изменения
- Бинарная несовместимость: существующие двоичные файлы могут столкнуться с критическими изменениями в поведении, такими как сбой при загрузке/выполнении или другое поведение во время выполнения.
- Исходный код несовместим: исходный код может столкнуться с критическим изменением поведения при нацеливании на новую среду выполнения/компонент/SDK, например ошибки компиляции или другое поведение во время выполнения.
- Изменение поведения: Существующий код и двоичные файлы могут вести себя по-разному во время выполнения.
Причина изменения
Текущее поведение привязки по умолчанию происходит независимо от настроенной среды и может привести к проблемам на компьютерах разработчиков, когда сертификат ещё не является доверенным (поскольку он самоподписанный). Клиенты часто получают негативный опыт при обращении к конечной точке HTTPS с ненадёжным сертификатом, например, тихий сбой, либо экран с ошибокой о ненадёжном соединении и т. д.
Рекомендованное действие
Если вы не использовали привязку https://localhost:5001 по умолчанию, никаких изменений не требуется. Однако, если вы использовали эту привязку, ознакомьтесь с этим руководством где описано, как вы можете обновить свой сервер, чтобы включить HTTPS.
Источник: 'https://github.com/aspnet/Announcements/issues/486'>github.com/aspnet/…sues/486UseStaticFiles дважды:
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions()
{
// Добавляем ещё папку
FileProvider = new PhysicalFileProvider(
Path.Combine(
builder.Environment.ContentRootPath,
"Docs"
))
});
На первый взгляд, это работает. Промежуточное ПО будет искать файлы в обычной папке wwwroot, а затем в папке Docs и возвращать их. Проблема возникает, если вы попытаетесь использовать что-нибудь, типа asp-append-version в представлениях для файлов из Docs. Версия просто не будет добавляться. Добавление версии в URL зависит от контрольной суммы файла. В этом случае система не может её найти, и этот атрибут просто будет проигнорирован.
StaticFiles на самом деле используют не просто промежуточное ПО для поиска файла, а полагаются на WebRootFileProvider. Но что такое FileProvider?
В документации есть хорошая статья об этом. Если коротко, это сервис, который знает, как находить файлы разными способами. Платформа имеет несколько реализаций, включая PhysicalFileProvider или ManifestEmbeddedFileProvider. В нашем плохом примере выше используется PhysicalFileProvider. А на самом деле UseStaticFiles использует WebRootFileProvider для поиска статических файлов (и этот провайдер просто ищет в wwwroot). Таким образом, реальный способ справиться с проблемой — изменить WebRootFileProvider приложения.
Итак, нам нужен способ использовать оба провайдера сразу. Сначала создадим нужные нам провайдеры (в данном примере два):
var webRootProvider =
new PhysicalFileProvider(
builder.Environment.WebRootPath
);
var docPathProvider =
new PhysicalFileProvider(
Path.Combine(
builder.Environment.ContentRootPath,
"Docs"
));
Теперь нам нужен третий тип, который позволит объединить наши провайдеры. Этот провайдер называется CompositeFileProvider:
var compositeProvider =
new CompositeFileProvider(
webRootProvider,
docPathProvider
);
И наконец, заменяем WebRootFileProvider нашим новым CompositeFileProvider:
app.Environment.WebRootFileProvider = compositeProvider; app.UseStaticFiles();Источник: wildermuth.com/2022/04…pnetcore
.csproj и имена папок (начиная с \), в которых это имя надо искать. Заметьте, что имена папок могут идти в любом порядке. Жирным в результатах подсвечены совпадения. Поиск можно настроить, отфильтровав по типу файла, добавив регулярные выражения и т.п. Отображение результатов также можно изменить.
И ещё парочка «хаков» для улучшения работы.
Смотрим содержимое нестандартных файлов
Хотя "Everything" позволяет посмотреть содержимое множества разных типов файлов, в том числе текстовых, Microsoft Office, картинок, .cs файлов и т.п., некоторые могут не поддерживаться. Например, файлы .csproj, как показано на картинке ниже. Чтобы это исправить, нужно внести небольшое изменение в реестр. Создайте файл <имя>.reg и добавьте в него следующее содержимое:
REGEDIT4 [HKEY_CLASSES_ROOT\.CSPROJ] "Content Type"="text/plain" "PerceivedType"="text"Сохраните файл. Затем двойным щелчком на файле внесите изменения в реестр. После этого этот тип файлов можно будет посмотреть в виде текста. Понятно, что это глобальная настройка отображения для Windows. "Everything" просто использует эти значения. Горячая клавиша с использованием клавиши Windows Можно назначить для нового поиска в "Everything" (да и для чего угодно ещё) горячую клавишу с кнопкой Windows, например,
Win+S. Поэтому его можно отключить, добавив значение в реестр. Аналогично предыдущей, это глобальная настройка.
Зайдите в Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced и добавьте новое значение Expandable String Value с именем DisabledHotKeys и значением S.
Перезагрузите Windows. После этого сочетание Win+S перестанет работать (если что, это глобальный поиск, его также можно открыть по Win+Q). После этого в "Everything" зайдите в Tools > Options > General > Keyboard и задайте для File | New Search Window значение Win+S. Некоторые сочетания с клавишей Windows сработают и без изменений в реестре (например, Win+Y или Win+J). Однако, возможно, в будущем им в Windows будет назначено какое-нибудь действие, тогда они работать перестанут.BlockingCollection<T> проектировался для создания таких коммуникационных каналов. По умолчанию BlockingCollection<T> работает в режиме блокирующей очереди и предоставляет поведение «первым зашел, первым вышел».
Блокирующая очередь должна совместно использоваться несколькими потоками, и обычно определяется как приватное поле, доступное только для чтения:
private readonly BlockingCollection<int> _bq = new BlockingCollection<int>();Обычно поток делает что-то одно: либо добавляет элементы в коллекцию, либо удаляет элементы. Потоки, добавляющие элементы, называются потоками-производителями, а потоки, удаляющие элементы, называются потоками-потребителями. Потоки-производители могут добавлять элементы вызовами Add, а когда поток-производитель завершится (когда будут добавлены все элементы), он может завершить коллекцию вызовом
CompleteAdding. Тем самым он уведомляет коллекцию о том, что элементы далее добавляться не будут, а коллекция может сообщить своим потребителям, что элементов больше не будет.
В следующем простом примере производитель добавляет два элемента, а потом помечает коллекцию как завершенную:
_bq.Add(7); _bq.Add(13); _bq.CompleteAdding();Потоки-потребители обычно выполняются в цикле, ожидая следующего элемента и выполняя его последующую обработку. Если выделить код производителя в отдельный поток (например, вызовом
Task.Run), то эти элементы можно будет потреблять следующим образом:
// Выводит "7", затем "13". foreach (int item in _bq.GetConsumingEnumerable()) Console.WriteLine(item);Если потребителей должно быть несколько,
GetConsumingEnumerable может вызываться из нескольких потоков одновременно. Тем не менее каждый элемент передается только одному из этих потоков. При завершении коллекции завершается и перечисляемый объект.
Во всех приведенных примерах GetConsumingEnumerable используется для потоков-потребителей; это самая распространённая ситуация. Но существует и метод Take, который позволяет потребителю получить только один элемент (вместо потребления всех элементов в цикле).
При использовании таких коммуникационных каналов необходимо подумать о том, что произойдет, если производители работают быстрее потребителей. Если элементы производятся быстрее, чем потребляются, возможно, придётся применить регулировку очереди.
Блокирующие очереди хорошо работают при наличии отдельного потока (например, из пула потоков), действующего как производитель или потребитель. Они не настолько хороши, если вы хотите обращаться к коммуникационному каналу асинхронно — например, если UI-поток должен действовать в режиме потребителя. Если вы вводите в своё приложение подобный коммуникационный канал, подумайте о переходе на библиотеку TPL Dataflow. Во многих случаях решение с использованием TPL Dataflow проще самостоятельного построения коммуникационных каналов и фоновых потоков.
Тип BufferBlock<T> из TPL Dataflow может работать как блокирующая очередь, к тому же TPL Dataflow позволяет построить конвейер или сеть для обработки. Впрочем, во многих простых случаях обычные блокирующие очереди (например, BlockingCollection<T>) станут более подходящим вариантом при проектировании.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 9.Program.cs лучше всё-таки вынести. Рассмотрим, как вернуть файл Startup.cs в проект минимальных API.
Вот пример файла Program.cs с использованием минимальных API:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();
if (!app.Environment.IsDevelopment()) {
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Здесь всё написано в одном классе Program.cs. Мы создаём построитель приложения, регистрируем зависимости, строим приложение и добавляем промежуточное ПО.
Startup.cs содержит 2 метода: ConfigureServices() для регистрации зависимостей и вызова промежуточного ПО в методе Configure().
Соответственно, всё, что идёт до строки
var app = builder.Build();должно попасть в ConfigureServices. А всё, что после – в Configure.
public class Startup {
public IConfiguration configRoot { get; }
public Startup(IConfiguration configuration) {
configRoot = configuration;
}
public void ConfigureServices(IServiceCollection services) {
services.AddRazorPages();
}
public void Configure(WebApplication app, IWebHostEnvironment env) {
if (!app.Environment.IsDevelopment()) {
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
}
}
Теперь нужно вызвать класс Startup из Program.cs:
var builder = WebApplication.CreateBuilder(args); var startup = new Startup(builder.Configuration); startup.ConfigureServices(builder.Services); var app = builder.Build(); startup.Configure(app, builder.Environment);Заметьте, что называть методы именами
ConfigureServices и Configure вовсе не обязательно. Тем более, что они похожи и часто путают (особенно новичков). Вы можете задать любые имена.
Источник: https://www.c-sharpcorner.com/article/how-to-add-startup-cs-class-in-asp-net-core-6-project/services.AddGrpc().AddJsonTranscoding();3. Добавьте аннотации файла .proto для привязки и маршрутов HTTP. Для этого сохраните себе файлы annotations.proto и http.proto в папку google/api вашего проекта и добавьте в ваш файл .proto строку
syntax = "proto3";
import "google/api/annotations.proto";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
get: "/v1/greeter/{name}"
};
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Транскодинг JSON или gRPC-Web
Оба варианта позволяют вызывать службы gRPC из браузера. Различаются способы достижения этого:
- gRPC-Web позволяет браузерным приложениям вызывать службы gRPC из браузера с помощью клиента gRPC-Web и Protobuf. Он требует, чтобы приложение создавало клиент gRPC, и имеет преимущество в отправке небольших и быстрых сообщений Protobuf.
- Транскодинг JSON позволяет приложениям вызывать службы gRPC как RESTful API с JSON. Приложению не нужно создавать клиент gRPC или знать что-либо о gRPC.
Реализация
Транскодинг JSON не является новой концепцией. grpc-gateway — это еще одна технология для создания RESTful JSON API из служб gRPC. Он использует те же аннотации .proto для сопоставления концепций HTTP со службами gRPC. Однако grpc-gateway использует генерацию кода для создания обратного прокси-сервера, который переводит вызовы RESTful в gRPC+Protobuf и отправляет их через HTTP/2 в службу gRPC. Преимущество этого подхода заключается в том, что служба gRPC не знает о RESTful API. Любой сервер gRPC может использовать grpc-gateway.
Транскодинг JSON выполняется внутри приложения ASP.NET Core. Он десериализует JSON в сообщения Protobuf, а затем напрямую вызывает службу gRPC. Это даёт преимущества для разработчиков приложений .NET:
- И службы gRPC, и сопоставленный RESTful JSON API выполняются из одного приложения ASP.NET Core.
- Транскодинг JSON десериализует сообщения JSON в Protobuf и напрямую вызывает службу gRPC. Выполнение этого в процессе даёт значительные преимущества в производительности по сравнению с новым gRPC-вызовом на другой сервер.
Что дальше
Транскодинг JSON в gRPC доступен в .NET 7 Превью 4. Это первый выпуск. Будущие версии .NET 7 будут сосредоточены на повышении производительности и поддержке OpenAPI.
Подробная информация доступна в документации. Также можно посмотреть пример приложения или видео с демонстрацией функционала.
Источник: devblogs.microsoft.com/dotnet/…r-dotnetCa) и исходящих («эфферентных» Ce) зависимостей:
I = (Ce / (Ca + Ce))Компонент, у которого нет исходящих зависимостей (он ни от чего не зависит), полностью стабилен; нестабильность = 0. Компонент, который зависит от многих других компонентов (и, возможно, не имеет компонентов, зависящих от него, что характерно, например, для многих точек входа приложений), будет иметь нестабильность, равную 1 или близкую к ней. Абстрактность пакета можно рассчитать, используя аналогичное соотношение абстрактных и конкретных классов:
A = Сумма(абстрактные классы)/Сумма(абстрактные + конкретные классы)В C# к абстрактным классам надо добавить и интерфейсы. Такие инструменты, как NDepend, могут быстро рассчитать стабильность и абстрактность для любого приложения .NET. Окончание следует… Источник: ardalis.com/what-ar…elopment Автор оригинала: Steve “Ardalis” Smith
public interface IOrderDataAccess
{
SqlDataReader ListOrders();
}
По определению, это абстракция. Вы не можете создать экземпляр этого типа; .NET считает его абстрактным. Он предоставляет модель для работы с данными, предположительно для получения информации о заказах. Но он явно не следуют DIP, потому что зависит от низкоуровневых деталей (очевидно, будет использоваться только для запросов к базам данных SQL)
Хороший интерфейс не должен ограничивать детали своей реализации. Интерфейсы определяют, что должно произойти; реализации определяют, как это сделать.
Мы можем заменить эту плохую абстракцию, следуя DIP, устранив зависимость от низкоуровневых деталей в определении интерфейса:
public interface IOrderDataAccess
{
IEnumerable<Order> ListOrders();
}
Обратите внимание, что вы можете легко реализовать этот интерфейс, используя любую реализацию (базу данных, файлы, веб-API, в памяти, что угодно). Это прямой результат того, что он следует принципу инверсии зависимостей.
Продолжение следует…
Источник: ardalis.com/what-ar…elopment
Автор оригинала: Steve “Ardalis” SmithlaunchSettings.json. Этот файл содержит один или несколько профилей запуска для вашего веб-приложения. Чтобы разрешить создание или подключение туннеля при запуске приложения, добавьте свойство createTunnel: true в профиль запуска. Например:
{
…
"profiles": {
"MyWebApp": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7130",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"createTunnel": true
}
}
}
При запуске или отладке приложения в VS, вместо запуска URL-адреса локального хоста будет запущен URL-адрес туннеля, который затем подключится к конечной точке локального хоста.
Когда веб-приложение запущено, браузер переходит не на URL локального хоста, а использует URL общедоступного туннеля вида https://<…>tunnels.api.visualstudio.com. Доступ к этому URL возможен извне вашей локальной среды. Вы можете поделиться URL с другом или коллегой, либо перейти на него с внешнего устройства (телефона или планшета). Чтобы упростить это, вы можете сгенерировать QR-код для URL-адреса в браузере Edge (если встать в поле URL в Edge, справа появится иконка для генерации QR-кода) и отсканировать код с телефона или планшета.
Проблемы
Это пока ранняя предварительная версия, поэтому вы можете столкнуться с некоторыми проблемами при использовании этого функционала. Лучший способ сообщить о проблемах — использовать встроенную поддержку «Сообщить о проблеме» в Visual Studio.
Среди известных проблем:
- Пока не поддерживаются аккаунты GitHub.
- Если у вас несколько аккаунтов в VS, доступ будет дан первому из них.
- Пока поддерживаются туннели только с анонимной аутентификацией.
- Иногда, когда клиент не следует перенаправлениям, могут возникать ошибки 302.
Источник: devblogs.microsoft.com/visuals…projectsConcurrentDictionary<TKey, TValue> является потокобезопасным гарантирует быстрый доступ в подавляющем большинстве сценариев. Его API сильно отличается от стандартного типа Dictionary<TKey, TValue>, поскольку он должен иметь дело с конкурентным доступом из многих потоков.
Запись
Чтобы задать значение для ключа, используйте метод AddOrUpdate:
var d = new ConcurrentDictionary<int, string>(); string newValue = d.AddOrUpdate( 0, key => "Zero", (key, oldValue) => "Zero" );Метод
AddOrUpdate выглядит сложно, так как должен делать несколько вещей в зависимости от текущего содержимого словаря.
Аргументы:
- ключ,
- делегат, преобразующий ключ (в данном случае 0) в значение, которое будет добавлено в словарь (в данном случае "Zero"). Вызывается, только если ключа не существует в словаре.
- делегат, преобразующий ключ (0) и старое значение в обновлённое значение, которое должно быть сохранено в словаре ("Zero"). Вызывается, если ключ уже существует в словаре.
AddOrUpdate возвращает новое значение для этого ключа (значение, которое было возвращено одним из делегатов).
Чтобы конкурентный словарь работал правильно, может оказаться, что метод AddOrUpdate должен вызвать один (или оба) делегата несколько раз. Такое бывает очень редко, но это возможно. А значит, делегаты должны быть простыми и быстрыми и не должны иметь побочных эффектов, т.е. должны только создавать значение, не изменяя никакие другие переменные в приложении.
Добавить значение в словарь можно и через индекс:
// Добавляет (или обновляет) ключ 0, // связывая с ним значение "Zero". d[0] = "Zero";Этот синтаксис не предоставляет возможности обновления значений на основании существующего значения. Но он проще и нормально работает, если известно значение, которое требуется сохранить в словаре. Чтение
bool keyExists = d.TryGetValue(0, out string current);
TryGetValue вернёт true и задаст значение current, если ключ был найден в словаре. Если ключ не найден, TryGetValue вернет false. Прочитать значение можно и через индекс, но при отсутствии ключа будет выдано исключение.
Помните, что в конкурентном словаре несколько потоков могут заниматься чтением, обновлением, добавлением и удалением значений; во многих ситуациях бывает трудно проверить, существует ключ или нет до того, как вы попытаетесь прочитать его.
Удаление
Аналогично чтению:
bool keyExisted = d.TryRemove(0, out string removed);Хотя
ConcurrentDictionary<TKey, TValue> является потокобезопасным, это не означает атомарности его операций. Если несколько потоков вызывают AddOrUpdate конкурентно, может оказаться, что два потока обнаружат отсутствие ключа, а затем оба одновременно выполнят своего делегата, создающего новое значение.
ConcurrentDictionary<TKey, TValue> хорошо работает при чтении и записи со стороны нескольких потоков в общую коллекцию.
Если обновления относительно редки, возможно, ImmutableDictionary<TKey, TValue> будет более подходящим вариантом.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 9.