tag:blogger.com,1999:blog-82639494083475495962024-03-16T19:50:45.949+01:00Object-Oriented Software DevelopmentLanguages, patterns, technologies, frameworks.Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.comBlogger278125tag:blogger.com,1999:blog-8263949408347549596.post-49428191713670544782024-02-23T12:47:00.002+01:002024-02-23T12:48:30.629+01:00Entity framework migrations under heavy load from a server farm<div>Entity Framework migrations are great. I particularily like the mechanism that prevents any query on the database until all pending migrations are applied. This solves a lot of issues and in most scenarios, you can even rely on the <b>MigrateDatabaseToLatestVersion</b> initializer in production. The initializer is smart enough to guard any instance of the dbcontext within the current process, this works correctly even from ASP.NET.</div>
<div>
Well, mostly. Problems start when you have a farm of many ASP.NET servers that connect to the very same database. Each single server runs its own migration. Under heavy load this possibly means that your database is concurrently migrated from multiple servers.
</div>
<div>Some time ago we almost had a disaster involving this scenario. A really busy app deployed on multiple servers was updated and, sadly, applying pending migrations was constantly failing. Yep, it was something specific in one of migrations but the result was as follows: one of servers tried to start the migration. Migration involved a heavy query that lasted a couple of seconds. All other servers were migrating too, trying to execute the very same heavy query. After the heavy query there was another lightweight query that was failing on the first server because the heavy query was pending on other servers. And as soon as any other server finished the heavy query, it immediately failed on the lightweight query because yet another server was just executing the heavy query.
</div>
<div>
There's a solution, though, involving <b>two custom initializers</b>. One just checks if there are pending migrations and throws. This one is configured as the default initializer. Another one actually migrates the database and is only invoked from a controlled environment, like a separate application or a specific controller/action.
</div>
<div>Some code:</div>
<pre>
public class DefaultDbContextInitializer :
IDatabaseInitializer<ExampleMigrationDbContext>
{
public void InitializeDatabase( ExampleMigrationDbContext context )
{
Configuration cfg = new Configuration(); // migration configuration class
cfg.TargetDatabase =
new DbConnectionInfo(
context.Database.Connection.ConnectionString,
"System.Data.SqlClient" );
DbMigrator dbMigrator = new DbMigrator( cfg );
if ( dbMigrator.GetPendingMigrations().Count() > 0 )
{
throw new MigrationsException( "pending migrations!" );
}
}
}
public class MigratingDbContextInitializer :
IDatabaseInitializer<ExampleMigrationDbContext>
{
public void InitializeDatabase( ExampleMigrationDbContext context )
{
Configuration cfg = new Configuration(); // migration configuration class
cfg.TargetDatabase =
new DbConnectionInfo(
context.Database.Connection.ConnectionString,
"System.Data.SqlClient" );
DbMigrator dbMigrator = new DbMigrator( cfg );
foreach ( string MigrationName in dbMigrator.GetPendingMigrations() )
{
Stopwatch watch = new Stopwatch();
watch.Start();
dbMigrator.Update( MigrationName );
watch.Stop();
}
}
}
</pre>
<div>The first default initializer is configured globally:</div>
<pre>
Database.SetInitializer<ExampleMigrationDbContext>( new DefaultMigrationDbContextInitializer() );
</pre>
<div>Because of that, any attempt to touch the database that has pending migrations will fail with an exception you can catch and show a message.</div>
<div>But then, somewhere in a controlled environment you call this:</div>
<pre>
var context = new ExampleMigrationDbContext();
var migrator = new MigratingDbContextInitializer();
migrator.InitializeDatabase( context );
</pre>
<div>
This works. After the database is migrated in the controlled way, the default initializer stops throwing and the app is back to running.</div>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-61251996368349314322024-02-22T10:16:00.001+01:002024-02-22T10:16:26.409+01:00ASP.NET MVC and Bootstrap error classes<div>Bootstrap requires specific error classes to be applied to inputs (e.g. <b>is-invalid</b>). MVC on the other hand has its own error classes (e.g. <b>field-validation-error</b>).
There are numerous ideas how to combine the two and this is another one.
</div>
<div>The idea is to dirty replace MVC error classes using reflection. A starting point would be to change the input's error class:</div>
<pre>
typeof( HtmlHelper ).InvokeMember(
nameof( HtmlHelper.ValidationInputCssClassName ),
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.SetField,
null,
typeof( HtmlHelper ),
new[] { "is-invalid" } );
</pre>
<div>
This one overwrites the const <b>HtmlHelper.ValidationInputCssClassName</b> from its default value (<b>field-validation-error</b>) to Bootstrap's <b>is-invalid</b>. Calling this early causes invalid inputs have Bootstrap class.
</div>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-71040485385980220692024-02-07T12:12:00.005+01:002024-02-07T12:14:21.970+01:00Cross-domain front channel federated Single Log Out <div>The SSO (Single Sign On) can be implemented in various ways, people use WS-Federation, SAML2 or OpenIdConnect, all these protocols are great and work like a charm.</div>
<div>What's troublesome is the SLO (Single Log Out). There are two possible approaches:
<ul>
<li><a href="https://openid.net/specs/openid-connect-backchannel-1_0.html">back-channel logout</a> consists in server-server communication. The identity provider calls the service provider directly, server-to-server, without any user browser support (thus the name "back-channel") and says "hey, it's me, the identity provider. The session 10b7e82f-4a1b-489f-9848-0d8babcd737f should be terminated." The service provider marks the session as terminated then. While this looks great, there's an implementation and performance cost - the service provider must be implemented in a specific way - the very each request from any user must be checked against the active sessions repository, just because the session can be terminated by a server-server call from the identity provider.
<li><a href="https://openid.net/specs/openid-connect-frontchannel-1_0.html">front-channel logout</a> consists in server-browser-server communication. The usual way of implementing this is by using nested iframes returned from the identity provider. This is where the problem is.
</ul>
</div>
<div>The problem of nested iframes is a new, escalating issue. The more restrictions are added to web browsers because of the more strict security policy, the more problems there are with nested iframes. I've blogged about an issue with FireFox <a href="https://www.wiktorzychla.com/2024/01/firefox-slowly-becomes-new-internet.html">preventing cookies with <b>SameSite=none</b></a> from being sent in cross-domain iframes. Who knows if other browsers will adopt this seemingly incorrect policy just because someone decides <i>safety</i> is more important than conforming to the spec.
</div>
<div>Anyway, because of how some web browsers start to act when cross-domain iframes are involved, marking your cookies with <b>SameSite=none</b> is no longer a solution. Instead, we've concluded that the idea of iframes has to be replaced by something else that would still allow us to use the front-channel logout but would not suffer from browsers quirks.
</div>
<div>The idea is as follows. The identity provider maintains a per-user-session list of service providers that were authenticated in current session (it maintains such lists anyway, it's not anything new). The new element here is that the list allows the identity provider to distinguish between an <b>old-service-provider</b> and a <b>new-service-provider</b>. Old service providers will be handled the old way, with nested iframes. New service providers will be handled by <b>redirecting to them and expecting they redirect back</b>.
</div>
<div>Let's first discuss <b>how</b> the identity provider is able to distinguish service providers. There are two possible approaches:
<ul>
<li><b>hard approach</b> - the identity provider can rely on a static whitelist configuration where each possible identity provider is configured and an extra bit of information is available in the configuration.
<li><b>soft approach</b> - the identity provider can still have a whitelist but the information about the service provider type is not a part of the configuration. Instead, whenever a service provider tries to signin, the signin protocol contains an extra bit of information that tells the service provider what type of service provider is currently processed. For us, this extra information in the signin was an extra key in the <b>wctx</b> parameter of the WS-Federation. The <b>wctx</b> allows the service provider to pass an extra info to the identity provider, of the form [key=value]. It's commonly used to pass something like <b>rm=1</b> (remember me=1) or <b>ru=....</b> (return url=....). We just add yet another extra key here, <b>nslo=1</b> (new single log out=1). In case of third-party identity providers, this extra key in wctx is just ignored. In case of a first-party, our own identity provider, we use this extra info from the wctx to mark service providers as old/new at the identity provider side.
</ul>
</div>
<div>And now, comes the important part, the actual log out implemented at service providers and identity providers.</div>
<div>In case of a <b>service provider</b>:
<ul>
<li>signin - a regular <b>wsignin1.0</b> together with extra <b>wctx=nslo%3D1</b>
<li>sign out - a regular <b>wsignout1.0</b>. In case of <b>wsignoutcleanup1.0</b>, the service provider is <b>obliged</b> to return back (302) to the identity provider (with <b>wsignout1.0</b>)
</ul>
</div>
<div>In case of an <b>identity provider</b>:
<ul>
<li>signin - pay attention to <b>wctx</b> and mark service providers as old/new (as explained above)
<li>sign out
</ul>
<pre>
service_provider = list_of_service_providers_of_new_type_from_session.FirstOrDefault();
if ( service_provider != null ) {
remove_from_session( service_provider );
redirect_to_service_provider_with_wsignoutcleanup1.0( service_provider )
} else if ( identity_provider_has_its_own_top_level_identity_provider ) {
create_page_with_iframes_to_old_type_service_providers();
redirect_to_top_level_identity_provider_with_wsignout1.0( top_level_identity_provider )
} else {
// there's no top-level identity provider above the current identity provider
create_page_with_iframes_to_old_type_service_providers();
close_session_and_return_information_page()
}
</pre>
</ul>
</div>
<div>Q&A
<ul>
<li><b>Why it works? </b> It works because there are no more nested iframes, instead the identity provider redirects to service providers and they redirect back. A 302 redirect always carries cookies.
<li><b>Why the distinction between old/new service providers?</b> It's because the log out is implemented as a redirect to the identity provider with <b>wsignoutcleanup</b>. Old-type service providers usually handle this by just terminating the local session and returning an empty response. Since now log out is a redirect from the identity provider to the service provider, the identity provider has to be sure the service provider will redirect back
<li><b>Is it backwards compatible with existing service providers / identity providers?</b> It is. An old-type identity provider (that just returns iframes) will ignore the extra signin info provided in wctx. The cross-domain sign out would probably still fail but you don't loose anything (it fails anyway). An old-type service provider with a new identity provider will not carry the extra wctx info so that it will be handled with an iframe (because the identity provider handles both type of service providers)
</ul>
</div>
Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-39618117097108836112024-02-01T09:30:00.000+01:002024-02-01T09:30:10.364+01:00ECMAScript modules in node.js<div>Node.js supports ECMAScript modules for few years now and if you still consider switching from the CommonJS, there are a couple of good arguments for.</div>
<div>First, <a href="https://nodejs.org/api/esm.html#enabling">enabling the module subsystem</a> is as easy as adding</div>
<pre>
...
"type": "module",
...
</pre>
<div>to the <b>package.json</b>. Then, modules can be exported/imported, both default and named conventions are supported:</div>
<pre>
// foo.js
function foo(n) {
return n+1;
}
function bar(n) {
return n-1;
}
function qux(n) {
return n-2;
}
export { bar };
export { qux };
export default foo;
// app.js
import foo from './foo.js';
import { bar, qux } from './foo.js';
console.log( foo(5) );
console.log( bar(5) );
console.log( qux(5) );
</pre>
<div>Modules can reference other modules recursively, the old good CommonJS supports cycles too but here it's even easier:</div>
<pre>
// a.js
import { b } from './b.js';
function a(n) {
if ( n > 1 ) {
console.log( `a: ${n}` );
return b( n-1 );
} else {
return 1;
}
}
export { a };
// b.js
import { a } from './a.js';
function b(n) {
if ( n > 1 ) {
console.log( `b: ${n}` );
return a( n-1 );
} else {
return 1;
}
}
export { b };
// app.js
import { a } from './a.js';
console.log( a(7) );
</pre>
<div>Modules support top-level <b>await</b></div>
<pre>
// app.js
console.log( await 7 );
</pre>
<div>And last but not least, modules interoperate with existing infrastructure</div>
<pre>
// app.js
import http from 'http';
import express from 'express';
var app = express();
app.get('/', (req, res) => {
res.end( 'hello world');
});
http.createServer( app ).listen(3000, () => {
console.log( 'started');
});
</pre>
Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-74517209067084043242024-01-25T13:44:00.001+01:002024-01-25T22:04:31.931+01:00Never, ever make your Random static<div>Years ago it was popular to advocate for sharing the instance of the <b>Random</b> class. There are numerous sources, including blogs, <a href="https://stackoverflow.com/questions/2643421/correct-method-of-a-static-random-next-in-c">the stack</a>, etc. Ocasionally, someone dared to write that it could not be the best idea. But still, looking for information, people stumle across this decade-old recommendation.
</div>
<div>That was the case of one of our apps. In one critical place, a shared instance of random was used</div>
<pre>
public static Random _rnd = new Random();
...
..._rnd.Next()...
</pre>
<div>
This worked. For years. Until one day, it failed miserably.
</div>
<div>
You see, the random has two internal variables it overwrites each time <b>Next</b> is called so that it can advance to the next random value. If your app is just winning a lottery ticket in its concurrent execution, the two values can end up being overwritten with the same value.</div>
<div>And guess what, random starts to return 0 each time <b>Next</b> is called! For us, it was the case of an internal stress test, where a heavy traffic was directed onto the application but it can happen just anytime.</div>
<div>There are numerous solutions. First, starting from .NET6 there's the <a href="https://learn.microsoft.com/en-us/dotnet/api/system.random.shared?view=net-8.0"><b>Random.Shared</b></a> static property, marked as thread-safe. In older frameworks, one of alternative approaches should be used, e.g. <a href="https://codeblog.jonskeet.uk/2009/11/04/revisiting-randomness/">this</a>
</div>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-21686363568161273542024-01-24T11:14:00.003+01:002024-01-24T11:14:51.608+01:00FireFox slowly becomes a new Internet Explorer<div>For years in the past, developing web apps was a constant case of <b>if (IE) do_something_else_than_for_other_browsers()</b>. Sadly, we lately have some bad cases where things seem similar, but instead of IE we now have FF.</div>
<div>One of earlier cases concerned the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Location/reload"><b>location.reload</b></a> API. In a specific case of an <b>iframe</b>, the app calls this to reload the content when users change their color theme. Worked everywhere instead of FF. As seen in the docs, FF has its own <b>forceGet</b> parameter, not supported in the <a href="https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-location-reload-dev">spec</a> but mentioned in the docs. Seems that <b>location.reload</b> works for us in FF only when this extra argument is provided.
</div>
<div>Another case appeared lately, unfortunately. Take the WS-Federation protocol and consider a scenario where the identity provider and the service provider are on different domains.</div>
<div>Signing in works correctly. The service provider redirects with <b>wa=wsignin1.0</b> and the identity provider responds with the SAML token POSTed to the service provider.</div>
<div>Signing out is implemented using nested <b>iframes</b> where the identity provider points to the service provider and adds <b>wa=wsignoutcleanup1.0</b> to terminate the session (drop the service provider session cookie). As you know, there's been a change lately in the way cross domain cookies are handled. To prevent the CSRF, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie"><b>SameSite</b></a> flag was added and in the default case, the cookie falls back to <b>SameSite=lax</b> which prevents it from being accessed cross different domains. </div>
<div>There's however still a way to make cookies available in cross domain requests, you are supposed to just mark them with <b>SameSite=none;Secure</b>. And guess what, <b>this works in all other browsers, except FF</b>. It turns out, the default security settings for FF prevent all crossdomain cookies, no matter if they are marked with <b>SameSite=none</b> or not.</div>
<div>Sure, users can opt out by lowering the security level or configure exceptions for your app but this doesn't change the fact that the specific scenario mentioned above just doesn't work in the default FF setup. Other browsers have their own security settings and, at least at the very moment, you can opt in for more strict settings (and cross domain cookies don't work anymore) but this requires a change in default settings. In case of FF, it's the default setting that it's against the samesite spec.
</div>
Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-62461784636144129782024-01-15T15:37:00.001+01:002024-01-15T15:37:36.640+01:00An interesting edge case in C# yield state machines<div>
Among many useful LINQ methods, there's no functional <b>ForEach</b>. People often <a href="https://stackoverflow.com/questions/200574/linq-equivalent-of-foreach-for-ienumerablet">implement it on their own</a>, an extension method is just a few lines of code.
</div>
<div>
Someone did that in one of our projects, too:
</div>
<pre>
public static void ForEach1<T>( this IEnumerable<T> enumeration, Action<T> action )
{
foreach ( T item in enumeration )
{
action( item );
}
}
</pre>
<div>This works great. Someone else, however, noticed that while this works great, it doesn't allow further chaining, since the method returns <b>void</b>
</div>
<div>
This <i>someone else</i> modified the code slightly:
</div>
<pre>
public static IEnumerable<T> ForEach2<T>( this IEnumerable<T> enumeration, Action<T> action )
{
foreach ( T item in enumeration )
{
action( item );
yield return item;
}
}
</pre>
<div>This doesn't work great. Frankly, it doesn't work at all. Surprising but true. Take this:</div>
<pre>
internal class Program
{
static void Main( string[] args )
{
Test1();
Test2();
Console.ReadLine();
}
static void Test1()
{
var array = new int[] { 1,2,3 };
var list = new List<int>();
array.ForEach1( e => list.Add( e ) );
Console.WriteLine( list.Count() );
}
static void Test2()
{
var array = new int[] { 1,2,3 };
var list = new List<int>();
array.ForEach2( e => list.Add( e ) );
Console.WriteLine( list.Count() );
}
}
public static class EnumerableExtensions
{
public static void ForEach1<T>( this IEnumerable<T> enumeration, Action<T> action )
{
foreach ( T item in enumeration )
{
action( item );
}
}
public static IEnumerable<T> ForEach2<T>( this IEnumerable<T> enumeration, Action<T> action )
{
foreach ( T item in enumeration )
{
action( item );
yield return item;
}
}
}
</pre>
<div>
While this is expected to produce 3 for both tests, it produces 3 and 0. The upgraded version doesn't work as expected.
</div>
<div>
The reason for this is a kind-of-a-limitation of the state machine generated by the <b>yield</b> sugar. The machine doesn't execute any code until the very result is enumerated. This means that changing
</div>
<pre>
array.ForEach2( e => list.Add( e ) );
</pre>
<div>to</div>
<pre>
array.ForEach2( e => list.Add( e ) ).ToList();
</pre>
<div>would "fix it". What a poor foreach, though, that requires an extra terminator (the <b>ToList</b>) and doesn't work otherwise.
</div>
<div>Luckily, a simpler fix exists, just forget the state machine at all for this specific case:</div>
<pre>
public static IEnumerable<T> ForEach2<T>( this IEnumerable<T> enumeration, Action<T> action )
{
foreach ( T item in enumeration )
{
action( item );
}
return enumeration;
}
</pre>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-67322706626816190012023-11-09T19:56:00.005+01:002023-11-09T20:29:27.619+01:00AmbiguousMatchException on a simple NET.Core Minimal API<div>One of my students pointed out to an unexpected issue in a simple .NET.Core app using the Minimal API</div>
<pre>
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapGet("/{param}", (string param) =>
{
return $"Parametr: {param}";
});
app.MapGet("/{param:int}", (int param) =>
{
return $"Parametr typu int: {param}";
});
app.MapGet("/{param:length(1,10)}", (string param) =>
{
return $"Parametr z ograniczeniem długości: {param}";
});
app.MapGet("/{param:required}", (string param) =>
{
return $"Parametr wymagany: {param}";
});
app.MapGet("/{param:regex(^\\d{{4}}$)}", (string param) =>
{
return $"Parametr spełniający wyrażenie regularne: {param}";
});
app.Run();
</pre>
<div>
The problem here is that routes overlap and it's not easily discoverable until a runtime exception is thrown when you try to navigate to a path that is matched by multiple endpoints. The <b>AmbiguousMatchException</b> is thrown.
</div>
<div>
My first attempt to workaround this was to wrap routes with <b>UseRouting</b> / <b>UseEndpoints</b>, hoping that this makes routing explicit. However, a closer look at the <a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-7.0#url-matching">documentation</a> reveals that
</div>
<div>
<b><i>
URL matching operates in a configurable set of phases. In each phase, the output is a set of matches. The set of matches can be narrowed down further by the next phase. The routing implementation does not guarantee a processing order for matching endpoints. All possible matches are processed at once. The URL matching phases occur in the following order.
</i></b>
</div>
<div>and also</div>
<div><b><i> there are other matches with the same priority as the best match, an ambiguous match exception is thrown.</i></b></div>
<div>This is kind of complicated to people coming with .NET.Framework background where even if routes overlap, a first matching route is picked up and its handler is executed. The .NET.Core introduces the idea of
<b>priorities</b> in routes, clearly covered by the documentation. In case there are multiple routes with the same priority, we have an exception.
</div>
<div>Fortunately enough, the docs also give at least one hint on what to do - a custom <b>EndpointSelector</b> can be used. A custom selector could just replace the <b>DefaultEndpointSelector</b> that is normally
registered in the service container, the default selector is useless as it's internal and its method is not virtual. But the custom selector is just a few lines of code.</div>
<pre>
using Microsoft.AspNetCore.Routing.Matching;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<EndpointSelector, CustomEndpointSelector>();
var app = builder.Build();
app.UseRouting();
app.MapGet( "/", () => "Hello World!" );
app.MapGet( "/{param}", ( string param ) =>
{
return $"Parametr: {param}";
} );
app.MapGet( "/{param:int}", ( int param ) =>
{
return $"Parametr typu int: {param}";
} );
app.MapGet( "/{param:length(1,10)}", ( string param ) =>
{
return $"Parametr z ograniczeniem długości: {param}";
} );
app.MapGet( "/{param:required}", ( string param ) =>
{
return $"Parametr wymagany: {param}";
} );
app.MapGet( "/{param:regex(^\\d{{4}}$)}", ( string param ) =>
{
return $"Parametr spełniający wyrażenie regularne: {param}";
} );
app.UseEndpoints( (endpoints) => { } );
app.Run();
class CustomEndpointSelector : EndpointSelector
{
public override async Task SelectAsync( HttpContext httpContext, CandidateSet candidates )
{
CandidateState selectedCandidate = new CandidateState();
for ( var i=0; i < candidates.Count; i++ )
{
if ( candidates.IsValidCandidate( i ) )
{
selectedCandidate = candidates[i];
break;
}
}
httpContext.SetEndpoint( selectedCandidate.Endpoint );
httpContext.Request.RouteValues = selectedCandidate.Values;
}
}
</pre>
<div>Please use this as a starting point to build a more sophisticated approach, fulfilling any specific requirements.</div>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-3502833262030082962023-10-19T10:38:00.002+02:002023-10-19T10:38:32.978+02:00Fido2.NetFramework<div>Just published a working code of a port of the <a href="https://github.com/passwordless-lib/fido2-net-lib">fido2-net-lib</a> library. The library is great, it works but there's a drawback - it targets
.NET 6 (or higher) which means that creating Fido2/Webauthn compatible website in .NET 4.X using the library is impossible.</div>
<div>My ported library, called the <a href="https://github.com/wzychla/Fido2.NetFramework"><b>Fido2.NetFramework</b></a> is supposed to fill the gap. I downgraded the code so that it compiles using the old C# 4.8 compiler, I replaced all external references that targets APIs/libraries/frameworks that are not available in .NET 4.8. For example, the <a href="https://github.com/ektrah/nsec">Nsec.Cryptography</a> used in the source library, was replaced with BouncyCastle.
</div>
<div>The code contains a working demo.</div>
<div>There's still some work to do, in particular, validate the port by porting unit tests and creating extra compatibility tests. </div>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-66959957411671713522023-08-28T13:41:00.001+02:002023-08-28T13:41:06.270+02:00Rust/WebAssembly vs Javascript performance on Mandelbrot animation rendering<div>The pending question <b>Is Rust/Wasm fast enough to even bother when comparing to Javascript?</b> has now yet another biased benchmark. I've decided to take one of my <a href="https://github.com/wzychla/julia-mandelbrot-demos">Julia/Mandelbrot demos</a>, benchmark it "as-is" and then, port to Rust and compare.</div>
<div>The HTML/Javascript source is short enough to post it here, the original demo code has some additional features (zooming) which are not present here</div>
<pre>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mandelbrot</title>
<script type="text/javascript">
var cx = 0, cy = 0, kat1 = 0, kat2 = 1;
var WJulia = 1024;
var HJulia = 1024;
var frame = 0;
var startDate;
var contextJulia;
var contextSmall;
var pixJulia;
var imgdJulia;
var iStart;
var iEnd;
var jStart;
var jEnd;
var iDelta;
var jDelta;
function setInitialScale()
{
iStart = -1.92;
iEnd = 1.92;
jStart = -1.92;
jEnd = 1.92;
iDelta = (iEnd-iStart)/1024;
jDelta = (jEnd-jStart)/1024;
startDate = new Date();
}
function SetupMandel()
{
var elemJulia = document.getElementById('JuliaCanvas');
if (elemJulia && elemJulia.getContext)
{
contextJulia = elemJulia.getContext('2d');
if (contextJulia)
{
imgdJulia = contextJulia.createImageData(WJulia, HJulia);
pixJulia = imgdJulia.data;
}
}
setInitialScale();
/* start */
requestAnimationFrame( LoopMandel );
}
function LoopMandel()
{
kat1 += 0.0021;
kat2 += 0.0039;
cx = .681 * Math.sin(kat1);
cy = .626 * Math.cos(kat2);
frame++;
document.getElementById('fps').innerHTML = `FPS: ${1000*frame/(new Date()-startDate)}`;
RysujMandel();
requestAnimationFrame( LoopMandel );
}
/* tworzenie bitowego obrazu */
function RysujMandel()
{
/* obliczenia */
var wi, wj;
var i, j;
var iterations = 255;
var px = 0;
for (i = iStart, wi = 0; wi < 1024; i += iDelta, wi++)
{
var py = 0;
for (j = jStart, wj = 0; wj < 1024; j += jDelta, wj++)
{
var c = 0;
var x = cx;
var y = cy;
while (((x*x + y*y) < 4) && (c < iterations))
{
[x, y] = [x * x - y * y + i, 2 * x * y + j];
c++;
}
SetPixelColor( pixJulia, (py * WJulia + px) << 2, 255, 255-c, 255-c, 255 - (c/2) );
py++;
}
px++;
}
/* kopiowanie bitowego obrazu do context/canvas */
contextJulia.putImageData(imgdJulia, 0, 0);
}
function SetPixelColor(pix,offs, a, r, g, b)
{
pix[offs++] = r;
pix[offs++] = g;
pix[offs++] = b;
pix[offs] = a;
}
window.addEventListener( 'load', SetupMandel );
</script>
</head>
<body>
<span id="fps" style='display:block'></span>
<canvas id="JuliaCanvas" width="1024" height="1024">
</canvas>
</body>
</html>
</pre>
<div>When run on my machine and the Edge Browser, it makes ~10-11 frames per second. Not bad assuming 1024x1024 image and 255 iterations.</div>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7kq-c-r-m5WUuclgOYNr0bTsXkSAIzOwcyfEEM1VSMSRfU6rIuBkM9ZKaJRaduqNlZgolokkqkwDe76Z7PdcRezIcMt5cBT2-qzK-J_7TXVccM5ryW32CTCOc727KwOB1hjKVRNVRs-EVzsgO4RaWAll1qFS_U0R2_KEjXnR9BhBuONbyyNI_N3TTXIGS/s1600/man.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="787" data-original-width="698" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7kq-c-r-m5WUuclgOYNr0bTsXkSAIzOwcyfEEM1VSMSRfU6rIuBkM9ZKaJRaduqNlZgolokkqkwDe76Z7PdcRezIcMt5cBT2-qzK-J_7TXVccM5ryW32CTCOc727KwOB1hjKVRNVRs-EVzsgO4RaWAll1qFS_U0R2_KEjXnR9BhBuONbyyNI_N3TTXIGS/s1600/man.png"/></a></div>
<div>Same code ported to Rust and compiled with <b>wasm-pack</b></div>
<pre>
// https://rustwasm.github.io/wasm-bindgen/examples/dom.html
use wasm_bindgen::prelude::*;
use wasm_bindgen::Clamped;
use web_sys::{CanvasRenderingContext2d, ImageData};
/* tworzenie bitowego obrazu */
#[wasm_bindgen]
pub fn mandel(
ctx: &CanvasRenderingContext2d,
width: u32, height: u32,
cx: f64, cy: f64) -> Result<(), JsValue> {
let i_start = -1.92;
let i_end = 1.92;
let j_start = -1.92;
let j_end = 1.92;
let i_delta = (i_end-i_start)/width as f64;
let j_delta = (j_end-j_start)/height as f64;
/* obliczenia */
let iterations = 255;
let mut pix_julia = Vec::with_capacity( usize::try_from(width * height).unwrap() );
let mut j = j_start;
for _wj in 0..height {
let mut i = i_start;
for _wi in 0..width {
let mut c: u8 = 0;
let mut x = cx;
let mut y = cy;
while ( (x*x + y*y) < 4.0) && (c < iterations)
{
let _tx = x;
x = x * x - y * y + i;
y = 2.0 * _tx * y + j;
c += 1;
}
pix_julia.push( 255-c );
pix_julia.push( 255-c );
pix_julia.push( 255-(c/2) );
pix_julia.push( 255 );
i += i_delta;
}
j += j_delta;
}
let data = ImageData::new_with_u8_clamped_array_and_sh(Clamped(&pix_julia), width, height)?;
ctx.put_image_data(&data, 0.0, 0.0)
}
</pre>
<div>The <b>cargo.toml</b>was</div>
<pre>
[package]
name = "hello-wasm"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.87"
[dependencies.web-sys]
version = "0.3.4"
features = [
'Document',
'Element',
'HtmlElement',
'Node',
'Window',
'ImageData',
'CanvasRenderingContext2d'
]
[profile.release]
opt-level = 3
lto = true
[package.metadata.wasm-pack.profile.release]
wasm-opt = ["-O4", "--enable-mutable-globals"]
</pre>
<div>and the HTML</div>
<pre>
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>Mandelbrot</title>
</head>
<body>
<script type="module">
import init, { mandel } from "./pkg/hello_wasm.js";
(async function() {
await init();
SetupMandel();
})();
var cx = 0, cy = 0, kat1 = 0, kat2 = 1;
var WJulia = 1024;
var HJulia = 1024;
var frame = 0;
var startDate;
var contextJulia;
var contextSmall;
var pixJulia;
var imgdJulia;
function SetupMandel()
{
var elemJulia = document.getElementById('JuliaCanvas');
if (elemJulia && elemJulia.getContext)
{
contextJulia = elemJulia.getContext('2d');
if (contextJulia)
{
imgdJulia = contextJulia.createImageData(WJulia, HJulia);
pixJulia = imgdJulia.data;
}
}
startDate = new Date();
/* start */
requestAnimationFrame( LoopMandel );
}
function LoopMandel()
{
kat1 += 0.0021;
kat2 += 0.0039;
cx = .681 * Math.sin(kat1);
cy = .626 * Math.cos(kat2);
frame++;
document.getElementById('fps').innerHTML = `FPS: ${1000*frame/(new Date()-startDate)}`;
mandel(contextJulia, WJulia, HJulia, cx, cy);
requestAnimationFrame( LoopMandel );
}
</script>
<span id="fps" style='display: block'></span>
<canvas id="JuliaCanvas" width="1024" height="1024">
</canvas>
</body>
</html>
</pre>
<div>And the result? Similar, it also makes 10-11 frames per second. Additional tests (different resolution and number of iterations) and possible optimizations can be applied here.</div>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-55415173368384524622023-07-20T11:08:00.001+02:002023-07-20T11:08:37.436+02:00Background music for coding - playlist<div>It's only an hour or so at the moment but nonetheless - this is a collection of music I listen while coding. I hope to expand the list in future.</div>
<a href="https://open.spotify.com/playlist/49F3btVzZx55zTOwtiIkKj?si=2770e2270f464e92">Link to the Spotify playlist</a>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-43576692630240567342023-07-20T09:34:00.008+02:002023-07-20T13:09:46.479+02:00Rust binary tree with iterator<div>For few days I scratch my head over Rust. Never tried it before, not going to be a Rust developer but I feel I'm missing new challenges and this looks like one.</div>
<div>While the official docs are rather concise, there are good books, the
<a href="https://www.amazon.com/Programming-Rust-Fast-Systems-Development/dp/1492052590/">Programming Rust: Fast, Safe Systems Development 2nd Edition</a> by Blandy/Orendorff/Tindall is really good
and it feels like it's written by folks who really like to help the reader. A lot of good examples and the background I missed in the official docs.</div>
<div>Anyway, my first successfull attempt at something not-that-obvious, a binary tree with an iterator. Doing this in C# would be a breeze, Rust had some surprises around every corner. I am lucky to have
<a href="https://zsacul.github.io/lpi-www/">Łukasz</a> who answers my questions, corrects my mistakes and gives critical hints on specific problems. Thanks!</div>
<div>The code below. For anyone wondering how much time did it take, starting from zero knowledge of the language to this point - few hours reading the book and then like 5-6 hours to get this result.</div>
<pre>
use std::fmt::Debug;
struct TreeNode<'a, T: 'a> {
val : &'a T,
left : Option<Box<TreeNode<'a, T>>>,
right: Option<Box<TreeNode<'a, T>>>
}
impl<'a, T> TreeNode<'a, T> {
fn new(
val: &'a T,
left: Option<Box<TreeNode<'a, T>>>,
right: Option<Box<TreeNode<'a, T>>>) -> Self {
Self { val, left, right }
}
}
/* C#'s IEnumerable on TreeNode - returns an iterator/enumerator */
impl<'a, T> IntoIterator for &'a TreeNode<'a, T> {
type Item = &'a TreeNode<'a, T>;
type IntoIter = TreeNodeIterator<'a, T>;
fn into_iter(self) -> Self::IntoIter {
TreeNodeIterator::init(&self)
}
}
/* C#'s IEnumerator */
struct TreeNodeIterator<'a, T: 'a> {
stack: Vec<&'a TreeNode<'a, T>>
}
impl<'a, T> TreeNodeIterator<'a, T> {
fn init(node: &'a TreeNode<'a, T>) -> Self {
Self { stack: vec![node] }
}
}
impl<'a, T> Iterator for TreeNodeIterator<'a, T> {
type Item = &'a TreeNode<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
if self.stack.len() > 0 {
let curr = self.stack.pop()?;
/* one way to tackle the child noe */
if let Some(right) = &curr.right {
self.stack.push( right.as_ref() );
}
/* another way of doing the same */
if curr.left.is_some() {
self.stack.push(curr.left.as_ref().unwrap());
}
return Some(curr);
}
None
}
}
/* auxiliary code to be able to test the enumerator */
struct TreeNodeUtility;
impl TreeNodeUtility {
fn do_work<'a, T> ( node: &'a TreeNode<'a, T> ) where &'a T: Debug {
for n in node {
println!("{:?}", n.val );
}
}
}
fn main() {
let s1 = String::from("1");
let s2 = String::from("2");
let s3 = String::from("3");
let root = Some( TreeNode::<String>::new(
&s1,
Some(Box::new(TreeNode::<String>::new(
&s2,
None,
None
))),
Some(Box::new(TreeNode::<String>::new(
&s3,
None,
None
)))
) ) ;
if root.is_some() {
let rootc = root.unwrap();
TreeNodeUtility::do_work( &rootc );
TreeNodeUtility::do_work( &rootc );
}
}
</pre>
<div>After consulting the <b>clippy</b> and trying to improve the code further, I came up with following changes:
<ul>
<li>changed the type of the iterator's item to <b>&T</b> from <b>&TreeNode<T></b>
<li>changed the <b>TreeNodeIterator</b> to a tupled struct (a struct with just a single field that doesn't even need a name)
<li>dropped the <b>init</b> method of the iterator struct
<li>changed the "if non empty stack - pop" code into a single liner
<li>changed the "if option is some - do" code into a single liner
</ul>
</div>
<pre>
use std::fmt::Debug;
struct TreeNode<'a, T: 'a> {
val : &'a T,
left : Option<Box<TreeNode<'a, T>>>,
right: Option<Box<TreeNode<'a, T>>>
}
impl<'a, T> TreeNode<'a, T> {
fn new(
val: &'a T,
left: Option<Box<TreeNode<'a, T>>>,
right: Option<Box<TreeNode<'a, T>>>) -> Self {
Self { val, left, right }
}
}
/* C#'s IEnumerable on TreeNode - returns an iterator/enumerator */
impl<'a, T> IntoIterator for &'a TreeNode<'a, T> {
type Item = &'a T;
type IntoIter = TreeNodeIterator<'a, T>;
fn into_iter(self) -> Self::IntoIter {
TreeNodeIterator(vec![self])
}
}
/* C#'s IEnumerator */
struct TreeNodeIterator<'a, T: 'a>(Vec<&'a TreeNode<'a, T>>);
impl<'a, T> Iterator for TreeNodeIterator<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if let Some(curr) = self.0.pop() {
/* one way to tackle the child node */
if let Some(right) = &curr.right {
self.0.push( right.as_ref() );
}
/* another way of doing the same */
if curr.left.is_some() {
self.0.push(curr.left.as_ref()?);
}
return Some(curr.val);
}
None
}
}
/* auxiliary code to be able to test the enumerator */
struct TreeNodeUtility;
impl TreeNodeUtility {
fn do_work<'a, T> ( node: &'a TreeNode<'a, T> ) where &'a T: Debug {
for n in node {
println!("{:?}", n );
}
}
}
fn main() {
let s1 = String::from("1");
let s2 = String::from("2");
let s3 = String::from("3");
let root = Some( TreeNode::<String>::new(
&s1,
Some(Box::new(TreeNode::<String>::new(
&s2,
None,
None
))),
Some(Box::new(TreeNode::<String>::new(
&s3,
None,
None
)))
) ) ;
if let Some( rootc ) = root {
TreeNodeUtility::do_work( &rootc );
TreeNodeUtility::do_work( &rootc );
}
}
</pre>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-72443583386117434882023-07-05T15:50:00.006+02:002023-07-05T15:56:39.287+02:00Notes to myself-from-the-future<p>The more code I write over the years, the more additional non-code information I tend to leave to myself from the future.
The additional information, put in inline comments, comments in signatures, external markdown files, is intended to be targeted exactly to me from the future.
</p>
<p>
It usually starts when I look for something. Like for example - an ASP.NET application, something that looks like it works upon each request but there's no visible sign of a public http module registered in <b>web.config</b>. Since that's odd, I go to the global.asax and try to find a module registered programmatically (rather than declaratively). And then I remember - "wait a minute, I remember myself
searching for the same information some time ago".
</p>
<p>This is where I make a conclusion - if I search for something and I remember searching for it at least once before, I try to think like <b>myself-from-the-future</b>. Where would I search for this information next time? Well, I am in global.asax now, so there's a high probability that the future-me will also come to the global.asax.
</p>
<p>I decide to use the global.asax then and I leave a note to myself-from-the-future - <b>If you search for [....], this information is in [....]"</b></p>
<p>Looking for various information, I then am grateful to <b>myself-from-the-past</b> - I often just stumble upon these "If you search for ... this information is in ...." which means that at some point in the past I tried to think like the present-me and my prediction at that time was correct. And often, I have no memory of writing these notes, I just see them in the code, I belive I wrote them at some point, I can even check the source control and find the exact date. But I just don't remember writing it at all.
</p>
<p>All these notes I send to myself, from the past to the future, are nice and useful and somehow let me feel like the past-me and the present-me is for sure the very same <b>me</b>, despite the time that passes. Things change but the way I think about myself is solid.
</p>
<p>Today was the day I tried to find something in the code. The code is an old desktop utility app that registers some HTTP listeners at start. There are multiple places listeners can be added, in the Main function, in the main form's constructor, in the main form's load event etc. At first I've assumed the myself-from-the-past would possibly leave a note to the present me somewhere.
</p>
<p>I've checked few obvious places. Is there a global markdown file for such notes? Nope. Is there a comment in the main <b>*.cs</b> file? Nope. Is there a comment in the form constructor? Nope. Is there a comment above the form class definition? Yes, there is but the <b>summary</b> section doesn't seem to contain any information about registration of HTTP listeners.
</p>
<p>So I've spent like 3 minutes to track down the registration. Found a method that does it. Found a reference to the method. Tracked the reference, until I've ended up in form's Load method.</p>
<p><i>Just leave a note to myself-from-the-future</i> - I thought. Next time I will try to search for it, I will probably go to main form class and a comment above it. The place that already contains the <b>summary</b> section but lacks the information about listener registrations.</p>
<p>So I go there, go to the very bottom of the <b>summary</b> section, since it spans a dozen of lines I carefully navigate to its ending to put a new line with the new information about the registration of HTTP listeners.
</p>
<p>And guess what. The <b>summary</b> section ends, the <b>remarks</b> section starts there, a section I obviously missed few minutes eariler (I just haven't noticed it's there, I thought the whole green block of comment lines was the <b>summary</b> section)!</p>
<p>And the only line in the <b>remarks</b> section is: <b>If you search for registration of HTTP listeners, it's in frmMain_Load</b></p>
<p>I swear it was not there few minutes ago ... But the fact that I've spent few minutes doing something I thought I hadn't done before but ultimately looks like I had, is kind of reassuring.</p>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-33220955753073325012023-06-01T10:39:00.002+02:002023-06-01T10:39:17.253+02:00C# Puzzle No.25 (intermediate)<p>
Let's have list of references to objects of a custom type that have a state. Let's loop through the list and alter the internal state of each object on the list.
</p>
<p>
Will the change of the internal state be preserved after the loop ends?
</p>
<p>
A first answer should of course be <b>yes</b>.
</p>
<p>
Take a look at this then:
</p>
<pre>
var elements =
Enumerable.Range(0, 10)
.Select( i => new Foo() { Bar = i });
foreach ( var e in elements )
{
// yields 0,1,2,3,4,5,6,7,8,9 which is correct
Console.WriteLine( e.Bar );
}
foreach ( var e in elements )
{
e.Bar += 10;
// yields 10, 11, ...., 19
Console.WriteLine( e.Bar );
}
foreach ( var e in elements )
{
// yields 0,1,2,3,4,5,6,7,8,9 which doesn't seem correct
// (original values restored)!
Console.WriteLine( e.Bar );
// expected: 10, 11, ..., 19!
}
public class Foo
{
public int Bar { get; set; }
}
</pre>
<p>
First we create a list of elements. Then we loop it to check if it's correct. Then we loop it to alter the internal state of objects.
</p>
<p>
Then we loop again and, oddly, it seems like original values are restored.
</p>
<p>
Not that obvious to spot what's wrong here, a similar issue was caught on production code. The goal is to find a simplest fix.
</p>
Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com1tag:blogger.com,1999:blog-8263949408347549596.post-80207001582340315032023-05-22T19:07:00.002+02:002023-06-01T13:52:41.676+02:00 Unable to generate an explicit migration because the following explicit migrations are pending<div>
There's an old SO <a href="https://stackoverflow.com/questions/9817860/unable-to-generate-an-explicit-migration-in-entity-framework">question about this</a>, the dreadful <b> Unable to generate an explicit migration because the following explicit migrations are pending</b>.
</div>
<div>
Not sure if SO answers cover this scenario but this is issue can occur in following setup:
</div>
<ul>
<li>you have a parameterless <b>DbContext</b>'s constructor and also a connection-string parameter constructor
<li>you generate migrations by adding the -ConnectionString and -ConnectionProviderName parameters of <b>add-migration</b>
<li>there's no default connection string in the configuration file
</ul>
<div>
The first migration is created correctly but the second one cannot find the migration already applied to the database. Seems it's a bug.
</div>
<div>
A simple solution is to add a default connection string to the configuration file and drop -ConnectionString and -ConnecitonProviderName from the invocation of add-migration. This picks the default connection string
and further checks all new migration against the default database. Still, the other constructor can be used in production code.
</div>
<pre>
public class FooDbContext : DbContext
{
public FooDbContext() { } // this constructor is used by migration generator
public FooDbContext( string connectionString ) : base( connectionString ) { } // you could be using this from your code
}
</pre>
Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-4476610157986413672023-03-23T08:45:00.005+01:002023-03-23T08:51:35.926+01:00Burning Mandelbrot set animation in Javascript<p>
Years ago I've blogged about <a href="https://www.wiktorzychla.com/2014/02/animated-javascript-julia-fractals.html">Julia fractals</a>, I've demonstrated how these can be easily
implemented in Javascript. I love to put my hands on this from time to time.
</p>
<p>
Another interesting experiment occurs when the Julia formula
</p>
<pre>
x0 = coordinates of a point from the plane
xn+1 = xn * xn + c
</pre>
<p>
is changed to
</p>
<pre>
x0 = 0
xn+1 = xn * xn + coordinates of a point from the plane
</pre>
<p>
This modified formula leads to the <a href="https://en.wikipedia.org/wiki/Mandelbrot_set">Mandelbrot set</a>. It's one of the most amazing discoveries of the computer science and has many
interesting properties.
</p>
<p>
The only subtle detail that can be improved here is that there's only one Mandlelbrot set, there's no obvious way to make an animation here. With Julias - the animation is created when the <i>c</i> parameter
is modified somehow, for example using the <a href="https://en.wikipedia.org/wiki/Lissajous_curve">Lissajous formula</a>, as shown in my old blog entry.
</p>
<p>
Can we somehow animate Mandelbrot? <b>Yes!</b> Just change the formula slightly
</p>
<pre>
x0 = c
xn+1 = xn * xn + coordinates of a point from the plane
</pre>
<p>
so instead of starting from 0, we start from a constant <i>c</i> that again can be modified using the Lissajous formula.
</p>
<p>
The result is interesting. I've called it the <b>Burning Mandelbrot</b> and honestly, I've never seen it before (although the idea is quite simple).
</p>
<p>
As a nice addition, I've implemented a simple zoom feature, you can zoom into the image by just clicking the point on the canvas. Enjoy!
</p>
<div>
<button style="width: 350px" id="resetButton">Reset the zoom factor</button>
</div>
<div>
X: <input style="width:150px" id="iStartLabel"><input style="width:150px" id="iEndLabel">
</div>
<div>
Y: <input style="width:150px" id="jStartLabel"><input style="width:150px" id="jEndLabel">
</div>
<canvas id="JuliaCanvas" width="768" height="768">
</canvas>
<script type="text/javascript">
var cx = 0, cy = 0, kat1 = 0, kat2 = 1;
var WJulia = 768;
var HJulia = 768;
var frame = 0;
var contextJulia;
var contextSmall;
var pixJulia;
var imgdJulia;
var iStart;
var iEnd;
var jStart;
var jEnd;
var iDelta;
var jDelta;
function setInitialScale()
{
iStart = -1.92;
iEnd = 1.92;
jStart = -1.92;
jEnd = 1.92;
iDelta = (iEnd-iStart)/768;
jDelta = (jEnd-jStart)/768;
}
function SetupMandel()
{
var elemJulia = document.getElementById('JuliaCanvas');
if (elemJulia && elemJulia.getContext)
{
contextJulia = elemJulia.getContext('2d');
if (contextJulia)
{
imgdJulia = contextJulia.createImageData(WJulia, HJulia);
pixJulia = imgdJulia.data;
}
}
setInitialScale();
resetButton.addEventListener('click', setInitialScale );
elemJulia.addEventListener('click', function(e) {
const rect = elemJulia.getBoundingClientRect()
var newI = iStart + (e.clientX-rect.left) * iDelta;
var newJ = jStart + (e.clientY-rect.top) * jDelta;
iDelta /= 2;
jDelta /= 2;
iStart = newI - (768/2) * iDelta;
iEnd = newI + (768/2) * iDelta;
jStart = newJ - (768/2) * jDelta;
jEnd = newJ + (768/2) * jDelta;
});
/* start */
requestAnimationFrame( LoopMandel );
}
function LoopMandel()
{
kat1 += 0.0011;
kat2 += 0.0029;
cx = .681 * Math.sin(kat1);
cy = .626 * Math.cos(kat2);
frame++;
RysujMandel();
requestAnimationFrame( LoopMandel );
}
/* tworzenie bitowego obrazu */
function RysujMandel()
{
/* obliczenia */
var wi, wj;
var i, j;
var iterations = 64;
var px = 0;
for (i = iStart, wi = 0; wi < 768; i += iDelta, wi++)
{
var py = 0;
for (j = jStart, wj = 0; wj < 768; j += jDelta, wj++)
{
var c = 0;
var x = cx;
var y = cy;
while (((x*x + y*y) < 4) && (c < iterations))
{
[x, y] = [x * x - y * y + i, 2 * x * y + j];
c++;
}
SetPixelColor( pixJulia, (py * WJulia + px) << 2, 255, 255-(c*4), 255-(c*4), 255 - (c*2) );
py++;
}
px++;
}
/* kopiowanie bitowego obrazu do context/canvas */
contextJulia.putImageData(imgdJulia, 0, 0);
iStartLabel.value = iStart;
iEndLabel.value = iEnd;
jStartLabel.value = jStart;
jEndLabel.value = jEnd;
}
function SetPixelColor(pix,offs, a, r, g, b)
{
pix[offs++] = r;
pix[offs++] = g;
pix[offs++] = b;
pix[offs] = a;
}
window.addEventListener( 'load', SetupMandel );
</script>
Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-62545818791356675052023-03-16T11:02:00.002+01:002023-03-16T11:02:58.529+01:00Common.Logging over log4Net and event specific properties preserved in async code<div>
There's an unfortunate issue in between the <b>Common.Logging</b> and <b>log4Net</b> we've stumbled upon.
</div>
<div>
The problem is related to so called <i>event specific properties</i>, additional values that should be logged and are usually added per log call. In the pattern layout, these additional properties
are specified using the <a href="https://logging.apache.org/log4net/log4net-1.2.13/release/sdk/log4net.Layout.PatternLayout.html"><b>%P</b></a>, e.g.
</div>
<pre>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%newline%date [%thread] [%P{vFoo} %P{vBar}] %-5level - %message" />
</layout>
</pre>
<div>
In the above example, the two additional properties, <b>vFoo</b> and <b>vBar</b> are injected into the logger and their values are part of the log entries.
</div>
<div>
If log4net is used directly, you just set your properties, either in a global context (properties set once and shared), in a thread context or a <b>logical thread context</b>
</div>
<pre>
ILog logger = LogManager.GetLogger( typeof( Program ) );
// shared
GlobalContext.Properties["vFoo"] = 5;
GlobalContext.Properties["vBar"] = 5;
// per thread (doesn't work with async/await)
ThreadContext.Properties["vFoo"] = 5;
ThreadContext.Properties["vBar"] = 5;
// per logical thread (works correctly over async/await)
LogicalThreadContext.Properties["vFoo"] = 5;
LogicalThreadContext.Properties["vBar"] = 5;
</pre>
<div>
The distinction between the last two is a strict motivation to write this entry. Note that putting properties in a thread context (implemented with log4net using
<a href="https://learn.microsoft.com/en-us/dotnet/api/system.threadstaticattribute?view=net-8.0"><b>ThreadStatic</b></a>) could be useful in some
scenarios but doesn't support async/await, mainly, when the continuation runs in a different thread, properties are lost. That's what the logical thread context does, it's implemented using the
<a href="https://learn.microsoft.com/en-us/dotnet/api/system.runtime.remoting.messaging.callcontext?view=netframework-4.8.1"><b>CallContext</b></a>
and properties are properly preserved in async/await code.
</div>
<div>
Now, move to Common.Logging which is commonly used as a wrapper over specific loggers. Common.Logging logger supports global variables context and thread variables context:
</div>
<pre>
var log = LogManager.GetLogger( typeof( Program ) );
// shared
log.GlobalVariablesContext.Set( "vFoo", 5 );
log.GlobalVariablesContext.Set( "vBar", 5 );
// per thread
log.ThreadVariablesContext.Set( "vFoo", 5 );
log.ThreadVariablesContext.Set( "vBar", 5 );
</pre>
<div>
And where's the <b>logical thread context?</b>
</div>
<div>Nowhere, it's missing from Common.Logging. Namely ...</div>
<pre>
var log = LogManager.GetLogger( typeof( Program ) );
log.ThreadVariablesContext.Set( "vFoo", 5 );
log.ThreadVariablesContext.Set( "vBar", 5 );
log.Debug( "foo bar hello" );
await Task.Delay( 1 );
log.Debug( "foo bar hello 2" );
await Task.Delay( 1 );
log.Debug( "foo bar hello 3" );
</pre>
<div>
... will log both variables correctly only the first time and will log <b>(null)</b> in two subsequent calls.
</div>
<div>Problem here is that log4net is supposed to search for variables set only using one it's own three classes presented earlier, <b>GlobalContext</b>, <b>ThreadContext</b> or the <b>LogicalThreadContext</b>.
And when Common.Logging is used throughout the application stack, there's no way to call any of the three directly.
</div>
<div>Our approach was to create a proxy logger and replace the log manager factory with one that returns the proxy logger. The proxy logger would reimplement the <b>ThreadVariablesContext</b> to use
the other way of storing thread variables.
</div>
<div>Let's start with the factory. It's supposed to be defined down the stack, so that it can be used everywhere. Since it can't use log4net directly, we'll use the common pattern to make
the context factory injectable from within the Composition Root</div>
<pre>
public class VLogManager
{
static Func<IVariablesContext> _variablesContextFactory;
public static void SetVariablesContextFactory( Func<IVariablesContext> variablesContextFactory )
{
_variablesContextFactory = variablesContextFactory;
}
public static ILog GetLogger( Type t )
{
return new WrappedLogger( LogManager.GetLogger( t ), _variablesContextFactory() );
}
}
</pre>
<div>Now comes the proxy logger, defined somewhere near the custom log manager. The logger is not supposed to use log4net, too</div>
<pre>
public class WrappedLogger : ILog
{
private ILog _log;
private IVariablesContext _logicalThreadVariablesContext;
public WrappedLogger( ILog log, IVariablesContext localVariablesContext )
{
this._log = log;
this._logicalThreadVariablesContext = localVariablesContext;
}
public bool IsTraceEnabled { get => _log.IsTraceEnabled; }
public bool IsDebugEnabled { get => _log.IsDebugEnabled; }
public bool IsErrorEnabled { get => _log.IsErrorEnabled; }
public bool IsFatalEnabled { get => _log.IsFatalEnabled; }
public bool IsInfoEnabled { get => _log.IsInfoEnabled; }
public bool IsWarnEnabled { get => _log.IsWarnEnabled; }
public IVariablesContext GlobalVariablesContext { get => _log.GlobalVariablesContext; }
public IVariablesContext ThreadVariablesContext { get => this._logicalThreadVariablesContext; }
public INestedVariablesContext NestedThreadVariablesContext { get => _log.NestedThreadVariablesContext; }
public void Debug( object message )
{
_log.Debug( message );
}
public void Debug( object message, Exception exception )
{
_log.Debug( message, exception );
}
public void Debug( Action<FormatMessageHandler> formatMessageCallback )
{
_log.Debug( formatMessageCallback );
}
public void Debug( Action<FormatMessageHandler> formatMessageCallback, Exception exception )
{
_log.Debug( formatMessageCallback, exception );
}
public void Debug( IFormatProvider formatProvider, Action<FormatMessageHandler> formatMessageCallback )
{
_log.Debug( formatProvider, formatMessageCallback );
}
public void Debug( IFormatProvider formatProvider, Action<FormatMessageHandler> formatMessageCallback, Exception exception )
{
_log.Debug( formatProvider, formatMessageCallback, exception );
}
public void DebugFormat( string format, params object[] args )
{
_log.DebugFormat( format, args );
}
public void DebugFormat( string format, Exception exception, params object[] args )
{
_log.DebugFormat( format, exception, args );
}
public void DebugFormat( IFormatProvider formatProvider, string format, params object[] args )
{
_log.DebugFormat( formatProvider, format, args );
}
public void DebugFormat( IFormatProvider formatProvider, string format, Exception exception, params object[] args )
{
_log.DebugFormat( formatProvider, format, exception, args );
}
public void Error( object message )
{
_log.Error( message );
}
public void Error( object message, Exception exception )
{
_log.Error( message, exception );
}
public void Error( Action<FormatMessageHandler> formatMessageCallback )
{
_log.Error( formatMessageCallback );
}
public void Error( Action<FormatMessageHandler> formatMessageCallback, Exception exception )
{
_log.Error( formatMessageCallback, exception );
}
public void Error( IFormatProvider formatProvider, Action<FormatMessageHandler> formatMessageCallback )
{
_log.Error( formatProvider, formatMessageCallback );
}
public void Error( IFormatProvider formatProvider, Action<FormatMessageHandler> formatMessageCallback, Exception exception )
{
_log.Error( formatProvider, formatMessageCallback, exception );
}
public void ErrorFormat( string format, params object[] args )
{
_log.ErrorFormat( format, args );
}
public void ErrorFormat( string format, Exception exception, params object[] args )
{
_log.ErrorFormat( format, exception, args );
}
public void ErrorFormat( IFormatProvider formatProvider, string format, params object[] args )
{
_log.ErrorFormat( formatProvider, format, args );
}
public void ErrorFormat( IFormatProvider formatProvider, string format, Exception exception, params object[] args )
{
_log.ErrorFormat( formatProvider, format, exception, args );
}
public void Fatal( object message )
{
_log.Fatal( message );
}
public void Fatal( object message, Exception exception )
{
_log.Fatal( message, exception );
}
public void Fatal( Action<FormatMessageHandler> formatMessageCallback )
{
_log.Fatal( formatMessageCallback );
}
public void Fatal( Action<FormatMessageHandler> formatMessageCallback, Exception exception )
{
_log.Fatal( formatMessageCallback, exception );
}
public void Fatal( IFormatProvider formatProvider, Action<FormatMessageHandler> formatMessageCallback )
{
_log.Fatal( formatProvider, formatMessageCallback );
}
public void Fatal( IFormatProvider formatProvider, Action<FormatMessageHandler> formatMessageCallback, Exception exception )
{
_log.Fatal( formatProvider, formatMessageCallback, exception );
}
public void FatalFormat( string format, params object[] args )
{
_log.FatalFormat( format, args );
}
public void FatalFormat( string format, Exception exception, params object[] args )
{
_log.FatalFormat( format, exception, args );
}
public void FatalFormat( IFormatProvider formatProvider, string format, params object[] args )
{
_log.FatalFormat( formatProvider, format, args );
}
public void FatalFormat( IFormatProvider formatProvider, string format, Exception exception, params object[] args )
{
_log.FatalFormat( formatProvider, format, exception, args );
}
public void Info( object message )
{
_log.Info( message );
}
public void Info( object message, Exception exception )
{
_log.Info( message, exception );
}
public void Info( Action<FormatMessageHandler> formatMessageCallback )
{
_log.Info( formatMessageCallback );
}
public void Info( Action<FormatMessageHandler> formatMessageCallback, Exception exception )
{
_log.Info( formatMessageCallback, exception );
}
public void Info( IFormatProvider formatProvider, Action<FormatMessageHandler> formatMessageCallback )
{
_log.Info( formatProvider, formatMessageCallback );
}
public void Info( IFormatProvider formatProvider, Action<FormatMessageHandler> formatMessageCallback, Exception exception )
{
_log.Info( formatProvider, formatMessageCallback, exception );
}
public void InfoFormat( string format, params object[] args )
{
_log.InfoFormat( format, args );
}
public void InfoFormat( string format, Exception exception, params object[] args )
{
_log.InfoFormat( format, exception, args );
}
public void InfoFormat( IFormatProvider formatProvider, string format, params object[] args )
{
_log.InfoFormat( formatProvider, format, args );
}
public void InfoFormat( IFormatProvider formatProvider, string format, Exception exception, params object[] args )
{
_log.InfoFormat( formatProvider, format, exception, args );
}
public void Trace( object message )
{
_log.Trace( message );
}
public void Trace( object message, Exception exception )
{
_log.Trace( message, exception );
}
public void Trace( Action<FormatMessageHandler> formatMessageCallback )
{
_log.Trace( formatMessageCallback );
}
public void Trace( Action<FormatMessageHandler> formatMessageCallback, Exception exception )
{
_log.Trace( formatMessageCallback, exception );
}
public void Trace( IFormatProvider formatProvider, Action<FormatMessageHandler> formatMessageCallback )
{
_log.Trace( formatProvider, formatMessageCallback );
}
public void Trace( IFormatProvider formatProvider, Action<FormatMessageHandler> formatMessageCallback, Exception exception )
{
_log.Trace( formatProvider, formatMessageCallback, exception );
}
public void TraceFormat( string format, params object[] args )
{
_log.TraceFormat( format, args );
}
public void TraceFormat( string format, Exception exception, params object[] args )
{
_log.TraceFormat( format, exception, args );
}
public void TraceFormat( IFormatProvider formatProvider, string format, params object[] args )
{
_log.TraceFormat( formatProvider, format, args );
}
public void TraceFormat( IFormatProvider formatProvider, string format, Exception exception, params object[] args )
{
_log.TraceFormat( formatProvider, format, exception, args );
}
public void Warn( object message )
{
_log.Warn( message );
}
public void Warn( object message, Exception exception )
{
_log.Warn( message, exception );
}
public void Warn( Action<FormatMessageHandler> formatMessageCallback )
{
_log.Warn( formatMessageCallback );
}
public void Warn( Action<FormatMessageHandler> formatMessageCallback, Exception exception )
{
_log.Warn( formatMessageCallback, exception );
}
public void Warn( IFormatProvider formatProvider, Action<FormatMessageHandler> formatMessageCallback )
{
_log.Warn( formatProvider, formatMessageCallback );
}
public void Warn( IFormatProvider formatProvider, Action<FormatMessageHandler> formatMessageCallback, Exception exception )
{
_log.Warn( formatProvider, formatMessageCallback, exception );
}
public void WarnFormat( string format, params object[] args )
{
_log.WarnFormat( format, args );
}
public void WarnFormat( string format, Exception exception, params object[] args )
{
_log.WarnFormat( format, exception, args );
}
public void WarnFormat( IFormatProvider formatProvider, string format, params object[] args )
{
_log.WarnFormat( formatProvider, format, args );
}
public void WarnFormat( IFormatProvider formatProvider, string format, Exception exception, params object[] args )
{
_log.WarnFormat( formatProvider, format, exception, args );
}
}
</pre>
<div>
Note how boring the proxy is, it merely delegates everything to the logger it wraps. The only part that is reimplemented is the <b>ThreadVariablesContext</b>.
</div>
<pre>
public IVariablesContext ThreadVariablesContext { get => this._logicalThreadVariablesContext; }
</pre>
<div>
Now, let's move to the <b>Composition Root</b> of the application stack, the place close to <b>Main</b> or <b>Application_Start</b>, where all references are available. This
is where the variables context implementation can be provided
</div>
<pre>
public class LogicalThreadVariablesContext : IVariablesContext
{
public void Set( string key, object value )
{
LogicalThreadContext.Properties[key] = value;
}
public object Get( string key )
{
return LogicalThreadContext.Properties[key];
}
public bool Contains( string key )
{
return LogicalThreadContext.Properties[key] != null;
}
public void Remove( string key )
{
LogicalThreadContext.Properties.Remove( key );
}
public void Clear()
{
LogicalThreadContext.Properties.Clear();
}
}
</pre>
<div>
Note that this is where we <b>are allowed</b> to reference log4net! And the last piece - inject this class into the custom logger factory. Specifically, add this to <b>Main</b> or <b>Application_Start</b>
</div>
<pre>
VLogManager.SetVariablesContextFactory( () => new LogicalThreadVariablesContext() );
</pre>
<div>This is it. Now, anywhere in your application stack you can inject custom variables into the logger ...
</div>
<pre>
var log = VLogManager.GetLogger( typeof( Program ) );
log.ThreadVariablesContext.Set( "vFoo", 5 );
log.ThreadVariablesContext.Set( "vBar", 5 );
log.Debug( "foo bar hello" );
await Task.Delay( 1 );
log.Debug( "foo bar hello 2" );
await Task.Delay( 1 );
log.Debug( "foo bar hello 3" );
</pre>
<div>... and values are properly stored in the log. Note that the only change in client's code is the usage of the newly introduced log manager (<b>VLogManager</b> instead of the provided <b>LogManager</b>).
</div>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-37863435823223630082022-08-26T09:29:00.002+02:002023-06-01T13:51:51.180+02:00TypeScript puzzle No.2 (easy)<p>
Consider following code
</p>
<pre>
class Foo {
bar() {
return 1
}
}
const foo = new Foo
foo.bar()
</pre>
<p>
Now consider that for some reason we want to include a type assertion, rather than just <b>foo</b> we want <b>foo as Foo</b>.
<pre>
class Foo {
bar() {
return 1
}
}
const foo = new Foo
(foo as Foo).bar()
</pre>
<p>
Surprisingly, this one doesn't compile, the error points to the last line and says
</p>
<pre>
Block-scoped variable 'foo' used before its declaration
</pre>
<p>
As always, your goal is to explain the unexpected behavior here.
</p>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-11633496002539646362022-08-22T12:02:00.005+02:002022-08-26T14:22:49.547+02:00TypeScript's Conditional Type Operator example<p>
We all like good examples, examples which, when seen, make us grasp new ideas easier. I believe the notion of <a href="https://www.typescriptlang.org/docs/handbook/2/conditional-types.html">Conditional Types</a> in TypeScript is often demonstrated in a way that makes you wonder what's the real purpose of it. Let's just have yet another example that could be useful here.
</p>
<p>
Let's start with a function type, it could be a type of a custom function or a built in function.
<pre>
// a custom function
function foo( s: string, n: number, b: boolean | symbol) : void {
}
type FooType = typeof foo;
// FooType = (s: string, n: number, b: boolean | symbol) => void
// the built in function, Array.prototype.slice
type ArraySliceType = typeof Array['prototype']['slice'];
// ArraySliceType = (start?: number | undefined, end?: number | undefined) => any[]
</pre>
The two function types have a different number of parameters but with the TypeScript's type system, we can write an auxiliary type that just picks a type of specific parameter.
</p>
<p>
This is where the actual example begins. The initial part of the example is based on a code presented in <i>Programming TypeScript</i> by B. Cherny. Let's start with a type that picks a parameter that
comes, like, second on the list of parameters.
<pre>
type SecondArg<F> = F extends (a: any, b: infer B, ...c: any ) => any ? B : never;
</pre>
Let's comment that. The <b>SecondArg</b> type is a generic type that expects a single generic parameter, <b>F</b>. This single generic parameter type is checked to be a function (the <b>F extends ...</b> part) and
depending on the test, the conditional operator either returns an inferred type of the second argument (<b>infer B</b>) or fails (<b>never</b>).
</p>
<p>
Let's test this
<pre>
type SecondArgString = SecondArg<string>;
// never
type SecondArgFoo = SecondArg<FooType>;
// number
type SecondArgArraySlice = SecondArg<ArraySliceType>;
// number | undefined
</pre>
This works great and to me - it's enough to demonstrate how useful the <b>? :</b> conditional operator is. Using this technique we can pick a type of a specific argument of specific function type, which, as
far as I know, is not possible in languages like, say, C# or Java. In C#, if there's a function and the second argument of the function is, say, <b>int</b> and you have to declare a variable of this type,
you have to know this type in advance to even start writing code.
</p>
<p>
But, this example can easily be pushed forward, why just pick a specific, second parameter? Why not write a generic type that picks the argument we want, first, second, third? Let's go beyond the original
example from the book.
</p>
<p>The question is, can numbers (parameter indexes) can be arguments of generic types?
</p>
<p>The answer is, sure, it's TypeScript, literals can be types. Remember the part of the TypeScript tutorial when you learn that a string <b>'foo'</b> can also be a type that is just a subtype of <b>string</b>?
Well, <b>5</b> can be a type that's just a subtype of <b>number</b>.
</p>
<p>The only small technical issue is that our new generic type needs <b>two</b> generic arguments and we need constraints on both of them which the <b>? :</b> operator doesn't support directly (or I don't know
how to do it :).
</p>
<p>
The first approach involves nesting the <b>? :</b> operator so that the nested part introduces the constraint on the second argument.
<pre>
type NThArg<F, N> =
F extends (...c: infer C) => any
? (N extends number ? C[N] : never )
: never;
</pre>
Please take a while to compare this new type to the previous one. As you can see, the parameter list is expressed in a more concise way, we don't need to introduce specific parameters, instead we
hide them all under the spread (...) operator. But the other generic parameter, <b>N</b> is constrained to be a number (the <b>N extends a number</b> part) so that it can be used to just index the
signature type (the <b>C[N]</b>) part.
</p>
<p>
If nesting the conditional type operator bothers you as much as it bothers me, there's another version
<pre>
type NThArg2<F, N extends number> =
F extends (...c: infer C) => any
? C[N]
: never;
</pre>
This time there's no nesting of the conditional operator. Instead, the constraint on <b>N</b> is expressed directly in type signature which works here as the constraint on <b>N</b> doesn't really invole anything
fancy (no need for conditionals, infer, etc.)
</p>
<p>
Both operators work, please take your time to play around
<pre>
type FooTypeNth10 = NThArg<FooType, 0>
// string
type FooTypeNth11 = NThArg<FooType, 1>
// number
type NThArgSlice = NThArg<ArraySliceType, 1>;
// number | undefined
type FooTypeNth2 = NThArg2<FooType, 1>
// number
type NThArgSlice2 = NThArg2<ArraySliceType, 1>;
// number | undefined
</pre>
</p>
<p>
And just by the way, did you know that <a href="https://github.com/microsoft/TypeScript/issues/14833">TypeScript's type system is Turing Complete</a>
(please also take a look <a href="https://itnext.io/implementing-arithmetic-within-typescripts-type-system-a1ef140a6f6f">here</a>
and <a href="https://github.com/ghoullier/awesome-template-literal-types">here</a>
and <a href="https://github.com/ronami/HypeScript">here</a>)? This basically means that we could write
actual programs using types only and results would be computed by the compiler during compilation. Please check the linked article to find few possible ways this could be used (and no, it's not quite useful,
you wouldn't like to write code that runs on your type system).
</p>
<p>Happy coding.</p>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-21791664133761921022022-08-11T14:29:00.001+02:002022-08-11T14:37:23.293+02:00Node.js + Express + TypeScript (ts-node reloaded)<div>
This is a short follow up to one of my previous posts, the <a href="https://www.wiktorzychla.com/2022/04/nodejs-express-typescript.html">Node.js + Express + TypeScript</a> where a
tiny tutorial on writing node.js node with TypeScript was discussed.
</div>
<div>
This time I'd like to introduce the <a href="https://www.npmjs.com/package/ts-node"><b>ts-node</b></a> which basically wraps the TypeScript execution in a single pipeline so that there's no need to precompile the TypeScript to JavaScript.
</div>
<div>
One of ways to use the <b>ts-node</b> is to install it globally and run the TypeScript code with
<pre>
ts-node app.ts
</pre>
Note only that the <b>tsconfig.json</b> has to be present, just to configure TypeScript options.
</div>
<div>
However, to also be able to debug the TypeScript from the Visual Studio Code and still use the <b>ts-node</b> to execute the code, install the ts-node locally and create a <b>.vscode/launch.json</b> section
<pre>
{
"name": "ts-node",
"type": "node",
"request": "launch",
"args": ["${workspaceFolder}\\app.ts"],
"runtimeArgs": ["-r", "ts-node/register"],
"cwd": "${workspaceRoot}",
}
</pre>
</div>
<div>A tiny code to test this
<pre>
// app.ts
import * as http from 'http';
import express, { Express, Request, Response } from 'express';
var app: Express = express();
app.get('/', (req: Request, res: Response) => {
res.write('hello world');
res.end();
});
var server = http.createServer(app)
server.listen(3000);
console.log( 'started' );
</pre>
</div>
Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-16830995339933974012022-06-09T12:28:00.001+02:002022-06-10T12:55:46.797+02:00Cannot navigate to the symbol under the carret after updating VS 2022<div>
After VS 2022 has been updated, suddenly some projects refuse to provide navigation between tokens, you can't just click on anything and "Go to Definition..."
</div>
<div>
There are couple of suggested workarounds, including restarting VS, deleting <b>*.user</b> files or going to <b>Options/Debugging/Symbols</b> and invoking <b>Empty symbol cache</b>
</div>
<div>
A working solution has been found <a href="https://developercommunity.visualstudio.com/t/cannot-navigate-to-the-symbol-under-the-caret-3/505489#T-N523312">here</a> and it consists in updating the <b>Microsoft.Net.Compilers</b> package referenced in affected projects to a newer version.
</div>
<div>
This however, potentially causes another issue, this time on a build server, if you happen to rely on <b>msbuild</b>. This time, the <b>MSB4184</b> is thrown
</div>
<pre>
error MSB4184: The expression ""App_Start\RouteConfig.cs".GetPathsOfAllDirectoriesAbove()"
cannot be evaluated. Method 'System.String.GetPathsOfAllDirectoriesAbove' not found
</pre>
<div>Turns out, <a href="https://stackoverflow.com/questions/59276192/getpathsofalldirectoriesabove-cannot-be-evaluated-after-updating-net-framewor">this also has been observed</a>. The proposed solution is to uninstall the <b>Microsoft.Net.Compilers</b>, this breaks token resolution is VS, though.
</div>
<div>What seems to work for us is to have
<ul>
<li>Microsoft.Net.Compilers 2.10
<li>Microsoft.CodeDom.Providers.DotNetCompilerPlatform 3.60
</ul>
</div>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-39252767293473506572022-05-05T12:11:00.001+02:002022-05-05T12:11:18.674+02:00Visual Studio 2022, Environment/Startup<p>
What happens when you just open the Visual Studio? It displays a solution selector where you pick up one of latests solutions. This is the default behavior. This can be changed, if you go to <b>Options</b> and then go to <b>Environment/Startup</b> page, you can change this to just show an empty environment.
</p>
<p>People <a href="https://community.dynamics.com/crm/b/mrdavesdavidyackdynamicscrmblog/posts/customize-visual-studio-2019-startup">got used to it</a>, also, it's <a href="https://docs.microsoft.com/en-us/visualstudio/ide/reference/startup-environment-options-dialog-box?view=vs-2022">mentioned in the documentation</a>
</p>
<p>The only problem is that the <b>Environment/Startup</b> is not there in Visual Studio 2022.</p>
<p>Instead, it's been moved to <b>Environment/General</b>, unfortunately, it's at the very bottom of the page and can easily be missed because you need to actually scroll down the page to see it</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBCNhTD_9hmzGUmu0aaMQJwVnfxVURve50uwDLhPFzd5Ig9BGVp3zYvQdhEmbbv53jMaOAWs-YyMwtoeSrlnZO-09VtBCxQVCsNVTmZrBSBFa89-os06-ekpXXaoAWu46H-mY_IotgEL2NXLVO09OBfv_vBJ11Hxy4Ahmr8plYLjgu0ulfGveHYb3_Ww/s1600/EmptyEnvironment.jpg" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="434" data-original-width="744" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBCNhTD_9hmzGUmu0aaMQJwVnfxVURve50uwDLhPFzd5Ig9BGVp3zYvQdhEmbbv53jMaOAWs-YyMwtoeSrlnZO-09VtBCxQVCsNVTmZrBSBFa89-os06-ekpXXaoAWu46H-mY_IotgEL2NXLVO09OBfv_vBJ11Hxy4Ahmr8plYLjgu0ulfGveHYb3_Ww/s1600/EmptyEnvironment.jpg"/></a></div>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-34411464735418840362022-04-25T19:28:00.002+02:002022-04-25T19:28:11.806+02:00Node.js + Express + TypeScript<p>The Node.js + Express + Javascript is a great platform and this short tutorial shows how to add TypeScript to the picture.</p>
<p>Start with installing TypeScript compiler globally
<pre>
npm install typescript -g
</pre>
so that whenever you invoke <b>tsc</b> from the command line, the compiler follows your <b>tsconfig.json</b> and does its job. Then add the <b>tsconfig.json</b>
<pre>
{
"compilerOptions": {
"lib": [
"es2021", "DOM"
],
"module": "commonjs",
"target": "es2021",
"strict": true,
"esModuleInterop": true,
"sourceMap": true
}
}
</pre>
Note that we specifically ask the module subsystem to translate TypeScript's export/imports into node.js <b>commonjs</b> modules. We also turn on the <b>esModuleInterop</b> flag for better node.js compatibility.
</p>
<p>
Now install some typings for node and express (other typings should possibly also be installed, depending on what packages you are going to use.
<pre>
npm i --save-dev @types/node
npm i --save-dev @types/express
</pre>
</p>
<p>
Create the <b>app.ts</b> file, which will be the starting point of the app and add <b>.vscode/launch.json</b> with
<pre>
{
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}\\app.ts",
"outFiles": [
"${workspaceFolder}/**/*.js"
]
}
]
}
</pre>
Note how the entry point (<b>program</b>) is set up here. Because the Typescript compiler will generate source maps, you would be able to run&debug the Typescript code.
</p>
<p>
Writing Typescript is easy now. Create an example express app
<pre>
import * as http from 'http';
import express from 'express';
var app = express();
app.get('/', (req, res) => {
res.write('hello world');
res.end();
});
var server = http.createServer(app)
server.listen(3000);
console.log( 'started' );
</pre>
Note how <b>express</b> is imported and how a library module (<b>http</b>) is imported - in both cases, there's the full intellisense support in VS Code as well as the support from the Typescript compiler.
</p>
<p>
To compile the code just invoke
<pre>
tsc
</pre>
once. To run the compiler in the <b>watch</b> mode:
<pre>
tsc --watch
</pre>
</p>
<p>
To show how TS modules work, create a simple module, <b>m.ts</b> with
<pre>
class Example {
DoWork( s: string ) : string {
return s + ' from Example';
}
}
export default Example;
</pre>
and import it in the <b>app.ts</b>
<pre>
import * as http from 'http';
import express from 'express';
import Example from './m';
var app = express();
app.get('/', (req, res) => {
let e = new Example();
res.write(e.DoWork('hello world'));
res.end();
});
var server = http.createServer(app)
server.listen(3000);
console.log( 'started' );
</pre>
Note how <b>m.ts</b> is compiled to <b>m.js</b> and also note how <b>m.js</b> is imported in the <b>app.js</b> (compiled from <b>app.ts</b>).
</p>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0tag:blogger.com,1999:blog-8263949408347549596.post-34468941349922807292022-04-13T17:38:00.000+02:002022-04-13T17:38:11.118+02:00Simplest SAML1.1 (WS-Federation) Federated Authentication in .NET 6<div>
Years ago I've blogged on <a href="https://www.wiktorzychla.com/2014/11/simplest-saml11-federated-authentication.html">how to do the WS-Fed using code only in .NET</a>. The approach had multiple advantages over the static <b>WSFederationAuthenticationModule</b> (the WSFam) configured in <b>web.config</b>. The most important feature here is that you have the most control over what happens: how the redirect to the Identity Provider is created and how the SAML token response is consumed.
</div>
<div>
This is extremely useful in complex scenarios, e.g. in multitenant apps where tenants are configured individually or multiple identity providers are allowed or even when the federated authentication is optional at all.
</div>
<div>.NET 6 (.NET Core) has <a href="https://docs.microsoft.com/en-us/aspnet/core/security/authentication/ws-federation?view=aspnetcore-6.0">its own way of handling WS-Federation</a>, namely, it registers its own middleware (<b>AddWsFederation</b>) that can be controlled with few configuration options. However, I still feel I miss some features, like triggering the flow conditionally in a multitenant app. What I need is not yet another middleware but rather, a slightly more low-level approach following the basic principle: not use module/middleware but rather have a custom code in the logon controller/action:
</div>
<pre>
if ( !IsWsFedResponse() )
{
RedirectToIdP();
}
else
{
var token = GetTokenFromResponse();
var principal = ValidateToken();
AuthenticateUsingPrincipal(principal);
}
</pre>
<div>
Please refer to the blog entry I've linked at the top to see the approach presented there follows this.
</div>
<div>
Can we have a similar flow in .NET Core? Ignore the middleware but rather have a total control over the WS-Fed?
</div>
<div>
The answer is: <b>sure</b>. Since the docs are sparse and there are not-that-much examples, a decompiler is handy to just see how things are done internally so we don't rewrite anything that's already there.
</div>
<div>
So, just setup your cookie authentication and point your unauthenticated users to <b>/Account/Logon</b>. And then:
</div>
<pre>
public class AccountController : Controller
{
private KeyValuePair<string, string[]> Convert(KeyValuePair<string, StringValues> pair)
{
return new KeyValuePair<string, string[]>(pair.Key, pair.Value);
}
public async Task<IActionResult> Logon()
{
WsFederationMessage wsFederationMessage = null;
if (HttpMethods.IsPost(base.Request.Method))
{
var parameters = (await this.Request.ReadFormAsync()).Select(Convert);
wsFederationMessage = new WsFederationMessage(parameters);
}
if (wsFederationMessage == null || !wsFederationMessage.IsSignInMessage)
{
var signInMessage = new WsFederationMessage();
signInMessage.IssuerAddress = "https://issuer.address/tenant/fs/ls";
signInMessage.Wtrealm = "https://realm.address/tenant/account/logon";
var redirectUri = signInMessage.CreateSignInUrl();
return Redirect(redirectUri);
}
else
{
string token = wsFederationMessage.GetToken();
SecurityToken validatedToken;
var tokenHandler =
new SamlSecurityTokenHandler()
{
MaximumTokenSizeInBytes = Int32.MaxValue
};
var tokenValidationParameters = new TokenValidationParameters()
{
AudienceValidator = (audiences, token, parameters) =>
{
return true;
},
IssuerValidator = (issuer, token, parameters) =>
{
return issuer;
},
IssuerSigningKeyValidator = (key, token, parameters) =>
{
if ( key is X509SecurityKey )
{
X509SecurityKey x509Key = (X509SecurityKey)key;
// validate cert thumb
// return x509Key.Certificate.Thumbprint == "alamakota";
return true;
}
else
{
return false;
}
},
IssuerSigningKeyResolver = (token, securityToken, kid, parameters) =>
{
var samlToken = (SamlSecurityToken)securityToken;
var signature = samlToken.Assertion.Signature;
// rewrite this to handle edge cases!
var certificate = signature.KeyInfo.X509Data.FirstOrDefault().Certificates.FirstOrDefault();
var x509Certificate2 = new X509Certificate2(System.Convert.FromBase64String(certificate));
return new List<SecurityKey>()
{
new X509SecurityKey(x509Certificate2)
};
}
};
// if this succeeds - we have the principal
var validatedPrincipal = tokenHandler.ValidateToken(token, tokenValidationParameters, out validatedToken);
// we strip all claims except the user name to have a complete control over how the cookie is issued
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Name, validatedPrincipal.Identity.Name)
};
// create identity
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
await this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return Redirect("/");
}
}
}
</pre>
<div>
Note how the <b>IssuerSigningKeyValidator</b> delegate replaces the <b>IssuerNameRegistry</b> from the .NET Framework example (linked blog entry). Also note, that other configuration options of the
<b>TokenValidationParameters</b> can be changed freely here on a per-request basis.
</div>Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com1tag:blogger.com,1999:blog-8263949408347549596.post-46653739230546541982022-03-25T14:41:00.003+01:002022-03-25T14:41:27.707+01:00Guess6 published on GitHubDecided to polish the code a little bit and <a href="https://github.com/wzychla/Guess6">published it on GitHub</a>. Such small yet complete apps are a great sandboxes to test/show various programming techniques. Expect more features soon then (a redux store is the next step).Wiktor Zychlahttp://www.blogger.com/profile/04420514974154487039noreply@blogger.com0