今日已更新 412 条资讯 | 累计 19972 条内容
关于我们

标签:#dotnet

找到 51 篇相关文章

开发者

How I Built and Published a .NET NuGet Package for the Giant SMS API

A while back, I needed to integrate SMS into a .NET project. Giant SMS had a REST API, but no official .NET client. The only existing library was a PHP one from 6–7 years ago, and it only covered two methods: send and getBalance. So I built my own. It now has nearly 2,000 downloads on NuGet. Here's exactly how I did it. The Problem Wiring up raw HTTP calls to the Giant SMS API in every project gets repetitive fast: Manually setting Authorization headers Remembering which endpoints use token auth vs. username/password Deserializing responses every time Scattering credentials across your codebase I wanted something that felt native to .NET. Configure once in appsettings.json , register with DI, and just call a method. Designing the Public API The first decision was the interface. I wanted consumers to never touch HttpClient directly, and I wanted methods that mapped clearly to what the API actually does: public interface IGiantSmsService { bool IsReady { get ; } Task < SingleSmsResponse > SendSingleMessage ( string to , string msg ); Task < SingleSmsResponse > SendMessageWithToken ( SingleMessageRequest messageRequest ); Task < BaseResponse > SendBulkMessages ( BulkMessageRequest messageRequest ); Task < SingleSmsResponse > CheckMessageStatus ( string messageId ); Task < BaseResponse > GetBalance (); Task < SenderIdResponse > GetSenderIds (); Task < BaseResponse > RegisterSenderId ( RegisterSenderIdRequest senderIdRequest ); } Seven methods, the full surface of the API, no more, no less. The IsReady property is a small but useful addition. It lets consumers do a quick sanity check at startup rather than discovering a missing token on the first SMS send: csharp _isReady = !string.IsNullOrWhiteSpace(_connection.Token) && !string.IsNullOrWhiteSpace(_connection.Username); Handling Two Auth Methods This was the most interesting design challenge. The Giant SMS API uses two different authentication schemes depending on the endpoint: Token-based (Basic Authorization header) —

2026-06-06 原文 →
AI 资讯

TryParse Looks Like a Small Utility Method — Until You Realize It Prevents Entire Classes of Production Failures

Why Senior .NET Engineers Rarely Trust User Input Most beginner C## developers discover TryParse() while learning console applications. It usually appears during a simple exercise: Console . Write ( "Enter quantity: " ); string ? input = Console . ReadLine (); if ( int . TryParse ( input , out int quantity )) { Console . WriteLine ( $"Quantity: { quantity } " ); } At first glance, it looks like a convenience method. A safer version of Parse() . A small utility. Nothing particularly interesting. But experienced .NET engineers see something completely different. They see one of the earliest examples of defensive programming. Because software engineering is not about handling perfect input. It is about surviving imperfect input. And in production systems, imperfect input is the rule—not the exception. TL;DR TryParse() is not just a conversion method. It introduces some of the most important concepts in professional software development: Defensive programming Input validation Runtime safety Exception avoidance Financial precision Domain modeling Reliability engineering Understanding why TryParse() exists is often more valuable than learning how to use it. Every Value in C## Starts With a Type One of the first concepts developers learn is that every variable has a type. int quantity = 10 ; decimal price = 25.99M ; string productName = "Laptop" ; bool isAvailable = true ; Simple. Yet this idea is foundational. Because types are not just containers. They are contracts. Each type defines: Valid values Memory layout Available operations Precision guarantees Runtime behavior When you choose a type, you are making an architectural decision. Why decimal Exists Many developers ask: Why not use double for money? Because financial systems require precision. Consider: double a = 0.1 ; double b = 0.2 ; Console . WriteLine ( a + b ); Expected: 0.3 Reality: 0.30000000000000004 The issue comes from binary floating-point representation. For scientific calculations, this is acceptable. F

2026-06-04 原文 →
AI 资讯

F# vs C# 3 — Conclusions

What can I say. Anyone claiming that F# is good mostly for finance and data processing and C# for everything else, has probably never written a single line of practical F# code. In previous two parts of the article, I tried to demonstrate that with F# you can achieve the same goals as with C#, but with less verbose, repetitive, structural code. How it started. At some point, developers realized that global state with unrestricted data access causes many side effects, producing insecure, error-prone, and hard-to-maintain code as software grows larger. That is when the idea emerged to bring data and the code operating on it together into a single unit, restricting direct access to the unit’s internal state and making software more secure and predictable. This is how data encapsulation was born. Alongside encapsulation, abstraction was introduced — the process of hiding how behavior works. Encapsulation ( hiding data ) and abstraction ( hiding behavior ) remain two foundational pillars of Object-Oriented Programming. And that is how OOP has worked ever since — developers bring data and behavior together ( classes ) and define abstractions for them ( interfaces ). For example, for C# developers — including myself — this has become a daily routine. And we rarely question it, because OOP languages like C# leave us little choice but to structure code this way. But if you ask yourself whether this repetitive routine is always necessary, the answer is — no. You don’t need OOP concepts to build stateless, streamlined request–response, data-processing pipelines, because in such systems there is no long-lived state to hide and protect. You have a request, and almost immediately you have a response. After that, everything is gone. That is what I tried to demonstrate in the first two parts of this article by applying FP concepts. And even if you have a classical desktop application, you don’t always need to approach it in an OOP way. Functional programming handles side effects no

2026-06-03 原文 →
AI 资讯

You can't delete an event. GDPR says you must. Crypto-shredding is the truce.

Two rules that can't both be true Event sourcing has one rule: you never delete. You append. The log is the source of truth, and rewriting history is the cardinal sin. GDPR Article 17 has one rule too: when a user asks, you erase their personal data. Not "hide it," not "flag it deleted" — erase it, everywhere, including backups. Put an event-sourced system in front of a privacy regulator and those two rules collide head-on. The user's name, email, and address are baked into CustomerRegistered , AddressChanged , OrderPlaced — dozens of immutable events, replicated to read models, snapshotted, and sitting in every nightly backup you've ever taken. "Just delete the events" breaks event sourcing. "Never delete" breaks the law. Most teams discover this tension after they've committed to append-only. A word on why this isn't academic for me. I build from Germany. Article 17 is EU law — the GDPR, or DSGVO as we call it here — not a German invention, but Germany enforces it about as hard as anywhere in Europe: regional data-protection authorities that issue real fines, and "we were careful" has never been a defense that held up. That pressure is exactly why I wanted erasure to fall out of the architecture instead of being a promise I make to an auditor and then pray I can keep. Why "delete the row" doesn't actually erase anything Say you give in and hard-delete the events for one user. You've still got their data in: every read-model projection rebuilt from those events, every snapshot that rolled them up, every backup taken before the deletion, every replica and every export that already left the building. Chasing personal data across all of those, provably, on a 30-day regulatory clock, is a nightmare — and a single missed backup tape means you didn't comply. Physical deletion doesn't scale to a system designed to keep everything forever. Crypto-shredding: delete the key, not the data The trick is to stop trying to delete the data and instead delete the ability to read it

2026-06-03 原文 →
AI 资讯

When an old business web app needs IE mode, and when it does not

Not every old business web app needs a full Internet Explorer environment. That sounds obvious, but it is easy to miss when a legacy intranet, ERP, OA, or ASP.NET WebForms page fails in Chrome or Microsoft Edge. The first instinct is often to put the whole system into IE mode. Sometimes that is absolutely correct. Other times, the page mostly works in Chromium and only breaks on older JavaScript or DOM assumptions. The useful first step is to separate those two cases. Case 1: the page needs a real IE engine Use Microsoft Edge IE mode, a Windows virtual machine, remote desktop, or another managed legacy-browser path if the page depends on: ActiveX controls COM integration VBScript Trident or MSHTML rendering behavior Browser Helper Objects Java applets strict IE7 or IE8 document modes A Chrome extension or JavaScript compatibility layer should not be presented as a replacement for those requirements. If the workflow depends on the IE engine, the browser engine is part of the application runtime. Case 2: the page mostly works, but old browser assumptions fail There is another common category. The page loads in Chrome or Edge, authentication works, and the main UI appears, but a small set of old behaviors fails. Examples include: empty frameset entry pages loading pages that do not finish redirecting attachEvent window.event event.srcElement showModalDialog -style picker flows document.frames older WebForms date fields that call a calendar function on focus For maintained source code, the best answer is still to fix the application. Replace old event APIs, remove synchronous dialog assumptions, and modernize generated WebForms scripts where possible. But in many real organizations, the legacy page is owned by a vendor, frozen department system, or migration backlog. In that situation, a scoped compatibility layer can be worth testing before moving the whole workflow into IE mode. A low-risk triage sequence I use this sequence: Pick one legacy hostname. Pick one failing

2026-06-01 原文 →
AI 资讯

🚀 JWT sem hash forte de senha é armadilha — Argon2 + .NET fecham o ciclo

A stack de autenticação em .NET fica sólida quando separamos duas responsabilidades: ✅ Argon2id para guardar senhas (hash irreversível, lento, memória-intensivo) ✅ JWT Bearer para provar identidade depois do login ✅ Validação de iss , aud , exp e assinatura em cada request ✅ Segredos fora do repositório (ambiente / Key Vault) Se o ecossistema .NET já oferece hosting, APIs e pacotes maduros, combinar Argon2 (referência da Password Hashing Competition , testável em argon2.online ) com JWT é o caminho natural para microsserviços e Web APIs. Neste artigo, mostro o fluxo registo → login → token → rotas protegidas com foco no que implementar no dia a dia. ⚠️ Observação importante JWT não substitui Argon2. Nunca coloque senha ou hash no payload do token. Argon2 protege a credencial na base de dados; JWT é sessão assinada com expiração. 🧠 Visão Geral Aspecto Argon2 (senha) JWT (sessão) Foco Resistir a offline cracking Autorizar requests após login Onde vive Coluna password_hash na BD Header Authorization: Bearer Algoritmo Argon2id (OWASP) HMAC-SHA256 ou RSA (config) Ferramenta de estudo argon2.online docs Microsoft JWT Bearer Runtime Biblioteca .NET (ex.: Konscious Argon2) Microsoft.AspNetCore.Authentication.JwtBearer Erro clássico MD5/SHA rápido na senha Token sem validar aud / iss 🧩 O que o Argon2 resolve (camada 1) O Argon2 é o vencedor da Password Hashing Competition — hoje a referência para novas passwords . 1️⃣ Hash irreversível com Argon2id var hash = hasher . Hash ( password ); await store . CreateAsync ( email , hash ); ✅ Salt único por utilizador ✅ Parâmetros m , t , p documentados no próprio hash ✅ Verificação com tempo constante ( FixedTimeEquals ) 2️⃣ Calibrar custo com consciência Em argon2.online podes experimentar memory cost e iterations — útil em laboratório. 📌 Em produção usa biblioteca auditada (.NET), não hashes de utilizadores reais em sites públicos. 3️⃣ O que não fazer na senha ✅ Não “criptografar” senha com AES reversível ✅ Não MD5 / SHA-1 / SHA-256

2026-06-01 原文 →
AI 资讯

pypdf vs PdfPig: Text Extraction at Scale

Overview PDF text extraction is a common pre-processing step in data pipelines — ingesting research papers, legal documents, or reports before embedding or indexing. Both pypdf and PdfPig are pure managed-code parsers: no native binaries, no OCR, no system PDF renderer. They implement the same PDF specification operations in their respective languages. This makes the benchmark unusually clean: the performance difference is entirely due to language execution speed, not library architecture differences. Benchmark Setup 200 recent arXiv PDFs (mixed technical papers, 5–40 pages each). Tested on subsets of 10, 50, 100, and 200 files. Both libraries extract all text from all pages; output is validated for page-count agreement and character-count agreement within 15% (pypdf and PdfPig decode whitespace and encoding tables slightly differently). Results PDFs Pages Python (pypdf) .NET (PdfPig) Speedup 10 ~120 ~0.9 s ~230 ms 3.9× 50 ~600 ~4.2 s ~810 ms 5.2× 100 ~1,200 ~8.5 s ~1.4 s 6.1× 200 ~2,400 ~17 s ~2.7 s 6.2× The speedup grows slightly with corpus size, suggesting pypdf has a per-document startup cost that compounds as PdfPig's JIT gets warmer. Why PdfPig Is Faster PDF parsing is byte-heavy: every page is a stream of PostScript-like operators (move, show text, set font, etc.). Each operator must be lexed, looked up in a dispatch table, and executed against a graphics state machine. In Python, each operator dispatch is a Python method call — the CPython bytecode interpreter has overhead per call regardless of what the method does. In .NET, the JIT compiles the dispatch loop to native code the first time it runs; subsequent pages pay only the cost of the actual work. Additionally, PdfPig's content-stream parser operates on ReadOnlySpan<byte> — zero-copy slicing through the raw page bytes with no intermediate string allocations. pypdf builds Python string objects for each token. Key Code // PdfPig — zero-copy span-based page extraction public Result Extract ( string path )

2026-06-01 原文 →
开发者

NetworkX vs CSR + TensorPrimitives: PageRank on 28M Edges

Overview PageRank is the canonical graph algorithm. NetworkX implements it in pure Python — its dict-of-dict adjacency representation means every power-iteration step dispatches millions of Python attribute lookups. When the graph has 1.8 million nodes and 28.5 million edges (Wikipedia category hyperlinks), those lookups dominate the runtime. The .NET replacement uses a CSR (Compressed Sparse Row) matrix — two flat int[] arrays for the graph structure — and TensorPrimitives for the SIMD-accelerated normalization step inside each iteration. Benchmark Setup Five SNAP datasets of increasing size: Dataset Nodes Edges wiki-Vote 7,115 103,689 soc-Epinions1 75,879 508,837 web-Stanford 281,903 2,312,497 web-Google 875,713 5,105,039 wiki-topcats 1,791,489 28,511,807 Algorithm: power-iteration PageRank, damping=0.85, tol=1e-6. Both implementations converge to identical top-10 node rankings. Results Dataset Python (NetworkX) .NET (CSR) Speedup wiki-Vote (103k edges) ~0.8 s ~100 ms ~8× soc-Epinions1 (508k edges) ~8 s ~600 ms ~13× web-Stanford (2.3M edges) ~120 s ~5 s ~24× web-Google (5.1M edges) ~5.5 min ~12 s ~28× wiki-topcats (28.5M edges) ~47 min ~60 s ~47× The speedup grows with graph size because NetworkX's Python dispatch cost scales with edge count, while the CSR inner loop is a tight JIT-compiled SIMD pass. Why CSR Beats NetworkX NetworkX represents each node's neighbors as a Python dict. Iterating the adjacency in one power-iteration step means: Calling G.neighbors(node) — a Python method call Iterating a dict — unboxing int keys, chasing heap pointers Accumulating a float into another dict value — another boxing step That happens for every edge, every iteration, roughly 50–80 times to convergence. CSR collapses the graph to two arrays: rowPtr[n+1] (where each node's neighbors start) and colIdx[edges] (the neighbor list). Iterating neighbors of node v is a tight C loop from rowPtr[v] to rowPtr[v+1] . No Python objects, no dict hashing, no pointer chasing. Key Code // P

2026-06-01 原文 →
AI 资讯

How to build a reusable Excel export service in ASP.NET Core

This article will teach you how to export any list into Excel in C# using the ClosedXML library. Steps to complete Create the data model with dummy data that we'll export into Excel. Create ExportExcel interface methods that accept any type of List (using IEnumerable<T> ) and a Dictionary List and export a byte array. Create extension methods and convert the provided data into rows and columns (using DataTable ). Create a service class that implements the interface methods and export the data table into a Memory Stream (byte array) using ClosedXML . Create one API endpoint that exports data in memory into Excel. Create another endpoint that exports incoming (custom) request data into Excel. Wire up dependencies. Project structure ├── Program.cs ← Project startup & dependency injection │ ├── controllers / │ └── ExportToExcelController.cs ← API entry point ├── services / │ ├── IExportToExcelService.cs ← Export Excel interface │ └── ExportToExcelService.cs ← Export Excel concrete class │ ├── models / │ ├── Car.cs ← Car class definition & dummy data │ ├── ExcelResponse.cs ← Wrapper class for excel file name and data │ └── ExportExcelRequest.cs ← Request class for that accepts any kind of list that will be exported │ └── extensions / └── IEnumerableExtensions.cs ← Extension methods for List<T> and List<Dictionary> 1️⃣ Data model I've created the dummy data model to demonstrate the dynamic implementation. public enum FuelType { Petrol, Diesel, Electric, Hybrid } public class Car { public Guid Id { get; set; } public string Name { get; set; } public string Manufacturer { get; set; } public int YearProduced { get; set; } public string Color { get; set; } public FuelType FuelType { get; set; } public int HorsePower { get; set; } public int NumberOfDoors { get; set; } public bool AutomaticTransmission { get; set; } public double AverageFuelConsumption { get; set; } public int MaxSpeed { get; set; } public decimal Price { get; set; } public static List<Car> GetCars() { ... } }

2026-05-30 原文 →
AI 资讯

Accept the Official Hack: Build-Time OpenAPI Detection in .NET 10 Minimal APIs

It is straightforward to configure a minimal API to produce an OpenAPI document at build time . This runs the API during build, requests the OpenAPI document from it, and saves it to disk. The slightly trickier part is to put checks in Program.cs to exclude any startup code that cannot run at build time. This is typically done because configuration key/value pairs are not available at that time. For example: if (! isBuildTime ) { connString = builder . Configuration . GetConnectionString ( "AppDB" ) ?? throw new InvalidOperationException ( "Connection string 'AppDB' is not configured." ); builder . Services . AddDbContext < AppDbContext >( options => { options . UseNpgsql ( connString ); } ); } The question is how to deduce that the API has been launched at build time, i.e. isBuildTime should be true? The official way of doing this is to check that the assembly that invoked the API is "GetDocument.Insider" : var isBuildTime = Assembly . GetEntryAssembly ()?. GetName (). Name == "GetDocument.Insider" ; GetDocument.Insider.dll is the command line tool that automatically runs during build of the API if the .csproj includes the following reference: <PackageReference Include= "Microsoft.Extensions.ApiDescription.Server" Version= "10.0.7" > ... </PackageReference> This package is a shim. It only provides build targets and props and hooks into the build of the API to run the command-line tool dotnet-getdocument . This tool in turn runs the command line tool GetDocument.Insider that we check for. This is a pretty convoluted sequence: Microsoft.Extensions.ApiDescription.Server provides targets that run during build of the API. One of those targets runs the command line tool dotnet-getdocument That in turn runs the command line tool GetDocument.Insider That in turn runs the API and fetches the /openapi/v1/json (or other configured endpoint) to get the OpenAPI document and saves it to disk. Checking in Program.cs if the API was invoked by the assembly GetDocument.Insider.dll t

2026-05-30 原文 →
AI 资讯

Sidemark: Active Telemetry Comments for C#

OpenTelemetry has quietly become table stakes. That's a good thing, but if you've instrumented a real codebase, you know the tax. A method that does one obvious thing slowly fills up with StartActivity , SetTag , AddEvent , SetStatus . The bookkeeping of telemetry starts to drown out the intent of the code, and in review you spend half your time mentally separating "what this code does" from "what we report about what it does." It's easy to think "oh, but the framework takes care of this with auto-instrumentation", but if you talk to the experts in OTel, they'll go to great lengths to explain that auto-instrumentation is a floor, not a ceiling. Most of the value in telemetry comes from the custom instrumentation you add to your code that adds business context to your traces. And that custom instrumentation is the stuff that clutters up your code. Here's the kind of thing I mean: // before - ugly, obtuse, who put that there var orderId = order . Id ; Activity . Current ?. SetTag ( "orderId" , orderId ); Sidemark is my answer to that code-obfuscation problem: non-invasive instrumentation via what I'm calling Active Comments . // after - glorious, beautiful, basking in the light of the sun, closer to god, happy, satiated var orderId = order . Id ; //? The idea A small set of comment syntaxes - //? , //! , //?! - become ride-along annotations . They travel next to the code, get read at build time, and turn into the equivalent Activity calls in the compiled output. The code you read stays the code that does the work. The telemetry rides along instead of competing with your logic for attention. The framing is loosely inspired by Wallaby.js's Live Annotations , which project runtime values inline next to the code that produced them. Sidemark takes the same instinct in the other direction: comments as a write surface for instrumentation, rather than a read surface for debug values. Comments are an under-used channel for information about code that isn't itself code - and su

2026-05-29 原文 →