Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
163532 stories
·
33 followers

Introducing TX Sheet Control: Excel Compatible Spreadsheets and Interactive Dashboards

1 Share
We are pleased to introduce TX Sheet Control, the latest addition to the Text Control product family. TX Sheet Control brings together Excel-compatible spreadsheet editing, a powerful calculation engine, and interactive dashboards. Designed for integration into business applications, TX Sheet Control offers a combination of familiar spreadsheet functionality and new ways to organize, explore, and present workbook data.

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Text Control Proudly Sponsors .NET Conf 2026

1 Share
Text Control is proud to sponsor .NET Conf 2026, the annual virtual conference bringing together developers, Microsoft engineers, and industry experts from around the world. Taking place November 10–13, 2026, the event celebrates the official launch of .NET 11 and the technologies shaping the future of software development.

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Your Aspects, Faster: Advice Parameter Binding in PostSharp 2027.0

1 Share

Every OnEntry( MethodExecutionArgs args ) you have ever written pays three costs on every call: it allocates an object on the heap, it boxes the arguments and the return value into object, and it makes you cast them back. Our own Pattern Libraries stopped paying those costs a long time ago, because they use a mechanism we kept internal: advice parameter binding. With PostSharp 2027.0, this mechanism is public, documented and checked at build time.

What MethodExecutionArgs costs you

Here is a typical logging aspect written as a simple aspect. It logs the first argument, the return value, the exception and the duration of the method:

[PSerializable]
public sealed class LogAttribute : OnMethodBoundaryAspect
{
    public override void OnEntry( MethodExecutionArgs args )
    {
        Console.WriteLine( "Entering {0}({1}).", args.Method.Name, args.Arguments[0] );
        args.MethodExecutionTag = Stopwatch.StartNew();
    }

    public override void OnSuccess( MethodExecutionArgs args )
    {
        Console.WriteLine( "{0} returned {1}.", args.Method.Name, args.ReturnValue );
    }

    public override void OnException( MethodExecutionArgs args )
    {
        Console.WriteLine( "{0} failed: {1}", args.Method.Name, args.Exception.Message );
    }

    public override void OnExit( MethodExecutionArgs args )
    {
        var stopwatch = (Stopwatch) args.MethodExecutionTag;
        Console.WriteLine( "{0} took {1} ms.", args.Method.Name, stopwatch.ElapsedMilliseconds );
    }
}

It works, and it has worked for many years. But look at what it asks of the runtime:

  • One allocation per call. MethodExecutionArgs lives on the heap, and so does the Arguments collection.
  • Boxing. The Arguments collection stores the arguments with their own types, but exposes them as object, so reading an int through args.Arguments[0] boxes it. args.ReturnValue is an object too, so an int return value is boxed as well.
  • Casts. MethodExecutionTag is an object too, so the stopwatch must be cast back, and a wrong cast fails at run time.

On a method called a few times per request, nobody notices. On a hot path, the aspect becomes the most expensive part of the method.

The same aspect with bound parameters

With advice parameter binding, the aspect derives from MethodLevelAspect, and each advice method declares only the values it needs, as typed parameters. A binding attribute on each parameter says where the value comes from:

[PSerializable]
public sealed class LogAttribute : MethodLevelAspect
{
    [OnMethodEntryAdvice]
    public void OnEntry( [DeclarationName] string methodName,
                         [Argument( 0 )] object argument,
                         [State( StateScope.MethodInvocation )] out Stopwatch stopwatch )
    {
        Console.WriteLine( "Entering {0}({1}).", methodName, argument );
        stopwatch = Stopwatch.StartNew();
    }

    [OnMethodSuccessAdvice]
    public void OnSuccess<T>( [DeclarationName] string methodName, [ReturnValue] T returnValue )
    {
        Console.WriteLine( "{0} returned {1}.", methodName, returnValue );
    }

    [OnMethodExceptionAdvice]
    public void OnException( [DeclarationName] string methodName, [Exception] Exception exception )
    {
        Console.WriteLine( "{0} failed: {1}", methodName, exception.Message );
    }

    [OnMethodExitAdvice]
    public void OnExit( [DeclarationName] string methodName,
                        [State( StateScope.MethodInvocation )] Stopwatch stopwatch )
    {
        Console.WriteLine( "{0} took {1} ms.", methodName, stopwatch.ElapsedMilliseconds );
    }
}

The bindings in this example:

  • [DeclarationName] gives the name of the method. It is a constant, computed at build time, so there is no reflection call at run time.
  • [State] declares a typed variable shared by the advices for one execution of the method. The entry advice assigns it with out, and the exit advice reads it. There is no MethodExecutionTag and no cast.
  • [ReturnValue] on a generic parameter T receives the return value without boxing. PostSharp infers T for each target method.
  • [Argument( 0 )] reads the first argument. Here it is still boxed, because the parameter is object. Make the advice generic to avoid it, as shown below.
  • [Exception] gives the exception, typed as Exception.

The aspect is applied exactly as before, with [Log] on a method or with multicasting.

What PostSharp actually generates

Bound parameters are a build-time contract, so the woven code passes the values directly. Take a smaller profiling aspect, with an entry advice that starts a [State] stopwatch and an exit advice that prints the [DeclarationName] and the duration (see Aspect Development Concepts). PostSharp transforms the target method into code equivalent to this:

public void PlaceOrder( Order order )
{
    Stopwatch stopwatch;
    aspect.OnEntry( out stopwatch );
    try
    {
        // Method body.
    }
    finally
    {
        aspect.OnExit( "ProfileAspect.OrderService.PlaceOrder", stopwatch );
    }
}

The state is a local variable, the method name is a string literal, and the advices are plain method calls. Apart from the Stopwatch itself, the woven code allocates nothing.

Generic advices, inferred for each target

An advice method can be generic. PostSharp infers the type arguments separately for each target method, with rules similar to the type inference of the C# compiler, and then calls a different instantiation of the advice from each target.

Take the OnSuccess<T> advice above. On int Square( int value ), PostSharp calls OnSuccess<int>. On string Format( Order order ), it calls OnSuccess<string>. The same source method serves every signature, and an int stays an int all the way.

Inference also works through constructed types and constraints:

[OnMethodEntryAdvice]
public void OnEntry<T>( [DeclarationName] string methodName,
                        [Argument( 0 )] IEnumerable<T> items )
    where T : IComparable<T>
{
    Console.WriteLine( "{0} received {1} items, the first is {2}.",
                       methodName, items.Count(), items.FirstOrDefault() );
}

Applied to a method that takes a List<int>, this advice gets T = int: PostSharp matches IEnumerable<T> against the type of the argument, its base types and its interfaces, as C# would. The rules, in short:

  • No boxing. A value of a value type is passed as it is, and a ref, out or in parameter receives the location itself.
  • Common type. When several bound values give a type argument, it is the type to which all of them can be assigned. OnEntry<T>( [Argument( 0 )] T a, [Argument( 1 )] T b ) on M( string, object ) gives T = object.
  • Constructed types. T[], T?, IList<T> and the like are matched against the value’s type, its base types and its interfaces.
  • Constraints are checked. Including unmanaged, new() and constraints that refer to other type parameters.

When a type argument cannot be inferred for a target, or violates a constraint, the build fails with the error LA0220, which states the reason. Apply the advice above to a method that takes a List<object>, and you learn at build time that object does not implement IComparable<object>.

The type of a parameter can also use the type parameters of a generic aspect class. They are replaced by the type arguments of the aspect instance. See Generic advice methods for the complete rules.

Errors at build time, not in production

A simple aspect accepts any method, then fails at run time if it makes a wrong assumption, for instance by reading args.Arguments[0] on a method that has no parameters.

With bound parameters, PostSharp checks each advice against each target when it weaves the aspect. An advice that does not fit a target is a build error with a message code. Apply the LogAttribute above to a method without parameters, and [Argument( 0 )] causes the error LA0225: the target method has no parameter at this index. A parameter of the wrong type, a binding used in an advice that does not support it, or a type argument that cannot be inferred all have their own codes. PostSharp reports all the errors of an aspect in one build.

The complete list of build messages is in the reference.

Beyond the basics

The logging example uses five bindings. The Advice Parameter Reference lists them all. Among them:

  • Write access with ref and out. Replace an argument, set or replace the return value, or replace the exception.
  • [FlowBehavior] to return without executing the method, or to swallow an exception.
  • Async methods. [CurrentTask] gives the task that the caller observes, including for ValueTask methods, and [AsyncCallId] identifies one execution of the async method.
  • Interception. [Binding] gives an object that invokes the original method, field or property.
  • [This], [Declaration] and [DeclarationIdentifier] for the instance and the reflection object of the target.

Grouping also got simpler. [SelfPointcut] and the Master property are optional when the grouping of the advices is unambiguous, so an aspect that applies to a single method, field, property or event needs neither.

Should you modernize your aspects?

Not necessarily. Simple aspects remain fully supported, and you can keep the AdviceArgs signature in the following cases:

  • The aspect intercepts events, validates fields or parameters, or uses OnInvokeAsync. These advices do not accept bound parameters yet.
  • The aspect reads the value yielded by an iterator, which only MethodExecutionArgs.YieldValue gives.
  • The aspect is not on a performance-sensitive path, and you prefer the simpler programming model.

The other limitations are listed in the reference.

For the aspects that do run on hot paths, the conversion is mechanical. Change the base class to MethodLevelAspect or LocationLevelAspect, turn each override into an advice method with the matching attribute, replace each args member with a bound parameter, and build. PostSharp tells you what does not fit. The Modernizing Your Custom Aspects article gives the steps and a table that maps each member of MethodExecutionArgs, MethodInterceptionArgs and LocationInterceptionArgs to its binding.

Try it

Advice parameter binding is available from PostSharp 2027.0, which also adds support for .NET 11 and C# 15. See What’s New in PostSharp 2027.0 for the full list. Pick one aspect on your hottest path, convert it, and tell us how it went on GitHub.

Happy meta-programming with PostSharp!

This article was first published on a https://postsharp.net/blog under the title Your Aspects, Faster: Advice Parameter Binding in PostSharp 2027.0.

Read the whole story
alvinashcraft
14 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Better Navigation and Version Selection in Duende Docs

1 Share
The Duende documentation just got better! Our docs cover several products, and each one has their own version: IdentityServer, BFF, Access Token Management, IdentityModel and more. Too often that meant landing on a page about a feature your version doesn't have, or not finding what's new in the release you're about to move to. We changed how the docs navigation works to fix that.
Read the whole story
alvinashcraft
19 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Wasp 0.26: parallel worktrees, wasp show, docs updates, and safer auth defaults

1 Share
The command wasp version printing 0.26.0

On our road to Wasp 1.0, we are shipping incremental releases, each adding more toward our vision of Wasp as a full-stack JS/TS framework that is liked and useful for humans and agents alike.

Wasp 0.26 is out.

This release lets you run several Wasp apps on one machine at once, so each coding agent can work in its own worktree.

Additionally, each app in a parallel worktree now gets its own set of ports, auth flows are getting safer defaults, the docs structure has been revamped, and wasp show now prints your app as Wasp sees it.

More on these below, so let’s dive in! 🐝

Parallel worktrees, ports and AI-assisted dev friendliness​

When you run several coding agents like Claude Code or Codex in parallel, a common setup is to give each one its own Git worktree. Until this release, only one of those worktrees could run your app.

That’s because wasp start would always use the fixed ports 3000 and 3001, and wasp start db would always use 5432.

In reality, users ran multiple instances and port clashing was a real issue: wasp start db, for example, would simply give up when 5432 was taken. So now, wasp start picks the first available ports on its own. It’ll start with 3000 and 3001 as before and then work its way up. Similarly, wasp start db will do the same, starting at 5432, and wasp start will then find the port your database ended up on.

This also means that each agent can get its own DB instance and can avoid the hassle of sharing the data.

Once you’ve created a new worktree, to set it up, you can just run wasp install and copy your .env files. If your .env.server has a DATABASE_URL, leave it out, so the worktree gets its own dev database instead of the shared one. Then start the database in one terminal and the app in another:

# Terminal 1: keeps the database running
wasp start db
# Terminal 2
wasp db migrate-dev
wasp start

If you need a fixed port, that’s also possible and you can pass --client-port, --server-port or --db-port values. A special h/t to our community contributor for the last one, @Nihal-Ahamed-MS.

Also, Wasp now manages your dev ports and URLs, so if you set WASP_SERVER_URL or WASP_WEB_CLIENT_URL in .env.server, REACT_APP_API_URL in .env.client, or server.port in vite.config.ts manually, it will now fail.

We explain more and what to remove in our migration guide to 0.26.

And your coding agent can benefit from the same setup - you can just prompt it to start the app in its own worktree and check the changes it’s making there, while your app can stay open and running in another one.

To upgrade, run the following:

npm i -g @wasp.sh/wasp-cli@latest

We also suggest you take a look at our worktree guide and if you get stuck or want to share your experiences with us, drop by our Discord server.

The wasp show command​

We’ve seen users having very large Wasp app’s spec files and others have had specs spanning multiple other files making it difficult to see all your routes or queries in one place.

We changed that by introducing wasp show spec command. Running it will now print out your app the same way Wasp sees it - all your pages, routes, entities, actions, queries, APIs, CRUDs and even jobs.

$ wasp show spec
...
Routes
Name Path Destination Loading
LoginRoute /login LoginPage Lazy
RootRoute / MainPage Lazy
SignupRoute /signup SignupPage Lazy

Queries
Name Import Entities
getTasks { getTasks } from "./src/queries" Task
...

If you want to see more information about your last build, you can run wasp show build.

Just remember to run both of these commands in your project folder and if you want the output in a more structured way, just add --json and get it like that.

The new wasp show replaces our older wasp info so if you depend on it and your code gets the value from it, switch it to wasp show build --json and then read the value from lastCompile.waspVersion.

And, again, agents will benefit from this structured output, too. You can quickly ask them which pages require auth or which queries and actions use a specific entity before changing that entity, avoiding conflicts and issues down the road.

You can find both commands in our CLI reference.

Improved docs structure​

The six sections of the Wasp 0.26 docs sidebar: Getting started, Tutorial, Features, Advanced, Deployment and Migration guides
The new docs sidebar in Wasp 0.26

We improved our docs structure significantly as well.

In our previous version, 0.25, we had 11 top-level sections like Data Model, Project Setup or Miscellaneous. In 0.26 we regrouped them so it’s faster to find a page when you know what you are looking for.

The structure is now streamlined to 6 sections: Getting started, Tutorial, Features, Advanced, Deployment, and Migration guides.

We also shortened the names of numerous pages - for example, Custom HTTP API Endpoints is now just API routes.

Older links still work, all the newly moved pages redirect correctly, and the older docs keep their structure. Agents will also see the same structure since our llms.txt files follow the new sidebar.

Safer auth defaults​

Our users trust Wasp when it comes to auth - we make all those flows for you, so figuring out any issues and polishing some rough edges is our job. In 0.26 we are fixing several of those.

When you reset your password now, the account will be logged out on every device. Password reset will check the token before the new password, so someone without a valid link can’t probe your password rules.

Also, the resend limit for verification emails will now check the time when the last verification email went out, and not the last password reset email.

We also improved how we are handling more global and internationalized addresses like jürgen@münchen.de or those written in UPPERCASE. Additionally, onBeforeSignup now runs before userSignupFields for every supported signup method.

All this ships by default in 0.26 and no changes are needed from your side. The only thing that could possibly break your e2e tests is that email fields now use type="text", so select them with input[name="email"] instead of input[type="email"] if those were part of your testing scripts.

You can read more in our auth docs.

On our road to Wasp 1.0​

Before we get to the upgrade details: as we said at the top, we're on our road to Wasp 1.0. If you want to know the moment it lands, leave your email below and we'll let you know.

Get an email when Wasp 1.0 lands!

Before you upgrade​

With 0.26, we have introduced some more breaking changes. Our 0.26 migration guide covers most of them, but here are a few for your reference.

  • Wasp now type-checks your code directly when it compiles, so wasp compile, wasp start and wasp build stop on TypeScript errors. It checked your code before too, so most apps won't see new errors. If you get any, fix them before you continue.
  • tsconfig.src.json needs an update, and custom Dockerfiles must copy the generated types directory.
  • Deployed servers now need a PORT environment variable.
  • The server's liveness check moved from GET / to GET /up. Point your health checks at /up.
  • The database flags of wasp deploy fly now start with db-, for example --db-vm-size. Fly CLI 0.4.82 and Railway CLI 5.28.0 are the new minimum versions.
  • Internal server-only import paths moved under wasp/server/.... If your app imported one, switch to a documented import such as wasp/server/auth.

Full list of changes and improvements​

  • Custom APIs now support the PATCH and HEAD methods.
  • New Fly.io and Railway databases now use PostgreSQL 18. wasp deploy fly also has new --db-vm-memory, --db-vm-cpus and --db-vm-cpu-kind options.
  • You can now change more of tsconfig.src.json, such as strict, target and lib.
  • Link and NavLink now accept the same search values as routes.X.build, and wasp/client/router exports LinkProps and NavLinkProps.
  • New projects no longer open the browser on wasp start.
  • We fixed Wasp commands in projects with many files under src/. Previously, they could fail with "Too many open files".
  • We fixed wasp start and wasp db commands that hung in some edge cases.
  • We fixed TypeScript incremental builds. Previously, they could miss updated types.
  • The compiler now escapes user input in the code it generates.
  • We fixed Railway databases created with --db-image. Previously, they had no persistent volume. Existing databases are unchanged.
  • We upgraded the internal morgan package to fix three CVEs. Wasp's usage was not affected.

The full list is in the GitHub release notes.

Read the whole story
alvinashcraft
24 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Introducing the pipeline explorer

1 Share

The first time you open an unfamiliar Octopus project (inheriting it, joining a new team, or exploring a corner of your own instance you haven’t touched before), you’re looking at someone else’s decisions with none of the context that went into making them. Real projects pick up channels, triggers, approvals, and a growing list of targets over time.

The pipeline explorer brings a project’s configuration together in one place. It helps you understand how an Octopus project works, on one page.

It’s built for anyone who’s new to a project: joining a team, inheriting ownership, or exploring an unfamiliar corner of their own instance.

It visualizes your deployment pipeline and deployment process as workflow diagrams, showing you how a release gets triggered and how it moves through your environments depending on the channel.

What the pipeline explorer does

1. Pipeline visualization

The pipeline explorer presents your project’s deployment pipeline and deployment process as workflow diagrams. Seeing them visually lets you understand how a release is triggered and how it moves through your environments, without piecing that together from separate tabs.

Pipeline explorer showing a Git trigger automatically deploying to Staging, then a manual ServiceNow approval before a frozen Production environment

2. Process visualization

A project’s deployment process depends on settings that live outside its process, like lifecycles, deployment targets, credentials, and approvals. The pipeline explorer surfaces these connections directly, so you don’t need to already know where each one lives.

Pipeline explorer showing a deployment process step linked to its package, Git artifact, an expiring credential, and Kubernetes targets with health status

3. Configuration issues

The pipeline explorer flags conditions that could affect an upcoming deployment directly in the view where you’re already looking.

Kubernetes targets in the pipeline explorer flagged as Unhealthy, Warning, and Healthy

What it doesn’t do

The pipeline explorer doesn’t replace your existing process, variables, or settings pages. It’s a new view built on top of the same data, not a redesign of how you configure a project. Think of it as a project map.

Try it

Open any project and select Pipeline explorer from the navigation to see it for yourself. Next time you visit an unfamiliar project, this is where you’ll want to start.

Read the docs for more details.

Happy deployments!

Read the whole story
alvinashcraft
29 seconds ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories