Coder Social home page Coder Social logo

servicetitan / stl.fusion Goto Github PK

View Code? Open in Web Editor NEW
1.8K 108.0 106.0 114.55 MB

Build real-time apps (Blazor included) with less than 1% of extra code responsible for real-time updates. Host 10-1000x faster APIs relying on transparent and nearly 100% consistent caching. We call it DREAM, or Distributed REActive Memoization, and it's here to turn real-time on!

License: MIT License

C# 99.78% Batchfile 0.05% HTML 0.09% JavaScript 0.07% Shell 0.01%
blazor blazor-server blazor-webassembly realtime realtime-tracking real-time websockets caching caching-library caching-memory

stl.fusion's People

Contributors

adampaquette avatar alexis-kochetov avatar alexyakunin avatar alivedevil avatar crui3er avatar dependabot-preview[bot] avatar dependabot[bot] avatar frolyo avatar hypercodeplace avatar iqmulator avatar leonardo-ferreira avatar maheshwarist avatar meenzen avatar pintrarakha-st avatar riesvriend avatar themodem avatar timeshift92 avatar tyrrrz avatar wdichler avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stl.fusion's Issues

Issue: Submitting an object with an ICollection to a service results in 415 error (Only with WebAssembly)

Hi all,

Today I've been working on building an application based on the Blazor sample. To make everything a bit easier for me, rather then passing individual values I instead pass around full objects from the Frontend to the Backend.

For example in the Shop.razor file:

await ShopService.UpdateShop(locals.Current);

locals.Current is of type DbShop:

    public record DbShop : LongKeyedEntity
    {
        [Required, MaxLength(120)]
        public string Name { get; set; } = "";

        public ICollection<DbShopItem> ShopItems { get; set; } = new List<ShopItem>();

        [MaxLength(9999)]
        public string XPathPrice { get; set; } = "";
        [MaxLength(9999)]
        public string XPathAvailability { get; set; } = "";
        [MaxLength(9999)]
        public string AvailabilityText { get; set; } = "";
    }

When I execute this code with server side blazor everything runs fine, however if I run this with WebAssembly I see the following error:

{type: "https://tools.ietf.org/html/rfc7231#section-6.5.13", title: "Unsupported Media Type",…}
status: 415
title: "Unsupported Media Type"
traceId: "00-71d8e4fd07aae244b05d21e0e0d6c6b5-2b78ddb4e971ba46-00"
type: "https://tools.ietf.org/html/rfc7231#section-6.5.13"

This is the request url:

http://localhost:5005/api/shop/UpdateShop?shop=DbShop+{+Id+%3D+1%2C+Name+%3D+Alternatef%2C+ShopItems+%3D+System.Collections.Generic.List`1%5BSamples.Blazor.Common.Models.DbShopItem%5D%2C+XPathPrice+%3D+dinggggawefwe%2C+XPathAvailability+%3D+dinggggg%2C+AvailabilityText+%3D++}

What I do find strange is the fact that everything is posted in the URL rather then the body. Is this intended?

Add Stl.Async-way of awaiting WaitHandle

I'd like to see some general-purpose ready-to-use implementation of Task WaitOneAsync(this WaitHandle, CancellationToken).
Maybe going as far as returning a ValueTask, though that may be challenging here.

Proposed API:

Task WaitOneAsync(this WaitHandle, CancellationToken = default)
Task WaitOneAsync(this WaitHandle, int Timeout, CancellationToken = default)

[StateHasChangedAsync] System.NullReferenceException: 'Object reference not set to an instance of an object.'

Hello, how can fix, this error?

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=Blazorise
  StackTrace:
   at Blazorise.BaseComponent.get_ClassNames()
   at Blazorise.Bootstrap.Button.BuildRenderTree(RenderTreeBuilder builder)
   at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, Exception& renderFragmentException)
--- End of stack trace from previous location ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.HandleExceptionViaErrorBoundary(Exception error, ComponentState errorSourceOrNull)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
--- End of stack trace from previous location ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(Int32 componentId, RenderFragment renderFragment)
   at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
   at Stl.Fusion.Blazor.ComponentExt.<>c__DisplayClass11_0.<StateHasChangedAsync>g__Invoker|0() in C:\Projects\C#\Stl.Fusion\src\Stl.Fusion.Blazor\ComponentExt.cs:line 108

  This exception was originally thrown at this call stack:
    [External Code]
    Stl.Fusion.Blazor.ComponentExt.StateHasChangedAsync.__Invoker|0() in ComponentExt.cs

image

Use SourceGenerator instead of `Castle.DynamicProxy` to create proxy types?

When a client is published to a platform that does not support System.Reflection.Emit, (eg. IL2CPP in Unity, AOT compiled on iOS, futural Blazor based on Mono AOT, or any other AOT scenes.), dynamic proxy will surely not work. Thus static proxy should be used instead in order to cover more scenes. (It also helps a lot in performance.)

.NET 5 introduces a new SourceGenerator as an expanded feature of code analyzer, which allows code analyzers to generate additional sources to be compiled alongside the project. It can also be used as a code analyzer to validate the code of computed services (eg. Every method marked with ComputeMethodAttribute should be declared to be virtual and return some Task.).

There are more mature alternatives to SourceGenerator such as Mono.Cecil (that Fody uses to generate proxies), and Roslyn that Orleans uses to generate proxies. They could also be used before .NET 5 and C# 9.0 is formally released.

Add custom exception types

This is a "meta issue": currently I mostly throw exceptions of existing exception types from mscorlib. This isn't good in many cases, i.e. adding a few custom types would definitely make it easier to identify certain error scenarios from user code.

On a positive side, I always throw exceptions via Error.* methods, i.e. it's actually pretty easy to identify all the scenarios that would benefit from custom-typed exceptions & update them accordingly.

Issue: STL Fussion not able to deal with recursive loops in classes to serialize

I've been trying to build an app based on Stl.Fusion but I'm running into some issues with serialisation of data objects from Server to Client.

The reason being that my DbClasses contain a recursive loop. E.g.:

    public record DbShop : LongKeyedEntity
    {
        [Required, MaxLength(120)]
        public string Name { get; set; } = "";

        public ICollection<DbShopItem> ShopItems { get; set; } = new List<DbShopItem>();



        [MaxLength(9999)]
        public string XPathPrice { get; set; } = "";
        [MaxLength(9999)]
        public string XPathAvailability { get; set; } = "";
        [MaxLength(9999)]
        public string AvailabilityText { get; set; } = "";
    }

    public record DbShopItem : LongKeyedEntity
    {
        [Required, MaxLength(120)]
        public string Name { get; init; } = "";

        [Required, MaxLength(9999)]
        public string Url { get; init; } = "";

        [MaxLength(9999)]
        public string LastPrice { get; init; } = "";
        public bool LastAvailable { get; init; } = false;
        [MaxLength(9999)]
        public string LastAvailableText { get; init; } = "";


        [Required, ForeignKey("DbStonkyShop")]
        public long DbStonkyShopId { get; init; }

        //[Required]
        //public DbStonkyShop? DbStonkyShop { get; init; }
    }

Normally you can configure ASP.NET Core controllers to ignore this by adding the following code:

services.AddMvc().AddNewtonsoftJson(options =>
{
    options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
})

However when I do this I run into some issues where STL Fusion makes use of their own JSON serializers which ignore this configuration.

For example the JsonNetSerializer class has its own DefaultSettings. Which can't be changed.

It seems though that in other places the normal JsonConvert.Serialize(...) method is used. With this you can actually override defaults by using JsonConvert.DefaultSettings.

Sign the assemblies that could be signed

Mostly needed for .NET Framework projects that rely on assembly signing.

Everything except RestEase dependencies (i.e. except Stl.Fusion.Client & Blazor assemblies) can be signed.

Commands are executed twice?

Running the HelloCart sample from https://github.com/servicetitan/Stl.Fusion.Samples it can be observed that InMemoryProductService.Edit() is executed TWICE each time the client modifies a product (after initialization)

Steps to reproduce:

  • Clone https://github.com/servicetitan/Stl.Fusion.Samples
  • Run HelloCart example
  • Select option #1
  • After Initialize() has run, but before entering a new product=price in the console, place a breakpoint in InMemoryProductService line 9
  • Enter a product=price value in the console and hit Enter
  • Observe breakpoint being hit twice.

Live sample at https://fusion-samples.servicetitan.com/ is broken in wasm mode

When attempting to run the demo https://fusion-samples.servicetitan.com/ and selecting wasm.

Receive error

An error has occurred. This application may no longer respond until reloaded. [Reload](https://fusion-samples.servicetitan.com/)

console shows error

blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Arg_NullReferenceException
System.NullReferenceException: Arg_NullReferenceException
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.UpdateRetainedChildComponent(DiffContext& , Int32 , Int32 )
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForFramesWithSameSequence(DiffContext& , Int32 , Int32 )
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(DiffContext& , Int32 , Int32 , Int32 , Int32 )
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer , RenderBatchBuilder , Int32 , ArrayRange`1 , ArrayRange`1 )
   at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder , RenderFragment , Exception& )
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(RenderQueueEntry )
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()

blazor wasm only (no server side blazor) can't get auth schemes

I based my code on a new dotnet 6 wasm blazor project and as far as i can tell i implemented everything needed on both side trying my hardest to work out the parts that were serverside blazor in the samples vs wasm blazor and got the an app mostly working very similar to the blazor sample but the auth dropdown does not work because the schema list is empty. looking at the sample since it ran the host page serverside it uses c# code on the server to get the list of schemas using

var authSchemas = await _serverAuthHelper.GetSchemas(HttpContext);
    var sessionId = _serverAuthHelper.Session.Id.Value;

and then

<script>
        window.FusionAuth.schemas = "@authSchemas";
        window.FusionAuth.sessionId = "@sessionId";
    </script>

to set varable even the wasm version can access but if you are running a wasm with no serverside blazer the host page is only index.html so can't contain the c# serverside code to extract the values above.

Can you provide instructions or sample for a blazer example with out serverside blazer and auth support?

Are the locks and ConfigureAwait calls needed?

This is a question rather than an issue, so apologies if this is the wrong way to post it.

I was looking at the docs and samples and see a lot of places where they add lock statements and chain ConfigureAwait(false) onto tasks. Normally this isn't how I'd expect Blazor components to be authored, so wonder if you could clarify the reason. The typical advice we give is:

  • Don't use ConfigureAwait(false) in Blazor or any other .NET UI framework, because it means your code escapes from the UI framework's synchronization context. In the case of Blazor, this means the framework's exception handling logic will no longer work properly, and you could end up with unhandled exceptions that take down your entire server instance. While it's true that ConfigureAwait(false) provides an incredibly small perf benefit, the benefit is not significant and definitely isn't worth being in an unsupported scenario where there aren't any guarantees about error handling.
    • Note: for the same reason, don't use .ContinueWith anywhere, as that also behaves like ConfigureAwait(false). Instead, make sure all tasks use await to go on to their continuation.
  • As long as you're not using ConfigureAwait(false), the framework's synchronization context takes care of ensuring that only one thread at a time is processing your component's methods (within a single circuit). As such you no longer have to worry about using lock - you have the same simplified threading model as a JavaScript application, at least for instance methods on your components.

It seems like there's an opportunity to simplify the samples, as well as fixing the error handling problems, by eliminating those things.

Of course it's also possible that I'm misunderstanding something about how Stl.Fusion works and there are important reasons why those things are required. I'd be interested if this is something you could clarify. Thanks!

Get rid of duplicate messages in Publisher-Replica comm. channel

Currently there are no checks for duplicate / repetitive messages & they are truly sent. On the other hand, that's the reason I introduced LTag (~ ETag in web) in IComputed, i.e. there is a good way to do this: just track the most recently sent (LTag, IsConsistent) pair & send something like "same state as you saw it last time" in case it's the same.

Exponential growth of buffer size?

I couldn't help to notice here that the buffer pool grows exponentially by a factor of 2... isn't that kinda dangerous? I mean, you should be fine for the first 10-12 bits, but after that you will start moving/copying fairly large portions of memory...

Have you consider using a strategy similar to RecyclableStream? Where a pool of smaller buffers are provisioned and then concatenated when more memory is needed... The growth of the total allocated pool grows linearly (according to the blockSize) but the large buffers can also grow exponentially... This is the big gain over the MemoryPool by the way

Implement ConcurrentTimerSet + RefHolder.Hold / Release on top if it

Right now ComputedRegistry & ReplicaRegistry take care of KeepAlive too, i.e. they ensure the strong ref is held to the instance for a desirable period. Though this part could be generalized & completely separated as:

public interface ITimerSet<TKey> {
    Moment? TryGet(TKey key);
    bool Add(TKey key, Moment fireTime); 
    bool AddOrUpdate(TKey key, Moment fireTime, Func<TKey, Moment, Moment, bool>? mustUpdate = null);
    bool Remove(TKey key, Moment fireTime);
}

public class CoarseTimerSet<TKey> : ITimerSet<TKey> {
   private Action<TKey, Moment> _fireHandler;

   public int BucketCount { get; } // always power of 2, indicates how many buckets each level has
   public TimeSpan MinDelay { get; } // min precision, e.g. 1 second
   public TimeSpan MaxDelay { get; } // level count ~= log2(MaxDelay / MinDelay)

   ...
}

Long story short, to keep an object alive for KeepAlive time (as it's done in e.g. IComputed.Touch method), we add a "timer" with its key to CoarseTimerSet (assuming this timer set has no fire handler).

Improve Purge in ComputedRegistry & ReplicaRegistry

There is a number of problems:

  1. Default capacity should be ~ 100-200 in WASM (it's almost 8K now - I tuned it for server-side scenarios)
  2. Use precise counter instead of stochastic counter in WASM (1 core = no point)
  3. Use a separate dict + maybe something similar to generations for strong refs. Ideally, strong refs should be replaced to weak ones as quickly as possible & Purge cycles in this part must be fairly frequent (1s or so), otherwise -- combined w/ a huge capacity & stochastic counters -- it leads to issues like #7 .

Discussion: Allow for invalidation without recomputation

In real world application we do not control all the data we need to display in our applications and therefore don't know when they have updated. Let's take a 3rd party Api for example.

We could of cause invalidate the data every n minutes but that would trigger a complete recomputation of our user interface (or at least the parts that are effected).

It would be great if we had some kind of mechanism to invalidate the cached result of a ComputedMethod without triggering recomputation but instead, reevaluate the Method the next time the result is needed.

This would have to work all the way up to so the next layer would't just be cached.

Add step-by-step tutorial

I'm thinking of the following approach:

  • Root-level "tutorial" folder contains numbered sections (p1_simple_computed, p2_[...], etc.)
  • Section = a directory with a single file (or maybe N files, if needed) + readme.md
  • Launcher = https://github.com/dotnet/try

Authorization

In the replicated service scenario, is this going around ASP.NET Core authorization? It appears that when a computed value is re-computed, it wouldn't be sent through the normal authorization handlers that a normal request would go through - am I interpreting that incorrectly?

The StateChanged handler in StatefulComponentBase occasionally throws ObjectDisposedException

I first ran into this issue when modifying the TodoPage.razor, changing the page's livestate to be dependent on multiple layers of Computed methods. It appears that when the blazor component state is being invalidated though the Fusion framework, the the call to the component's StateHasChanged() is sometimes not scheduled correctly causing this exception.

Exception:

Cannot process pending renders after the renderer has been disposed. Object name: 'Renderer'.
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(Int32 componentId, RenderFragment renderFragment)
   at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
   at Stl.Fusion.Blazor.StatefulComponentBase.<>c__DisplayClass26_0.<.ctor>b__1() in /_/src/Stl.Fusion.Blazor/StatefulComponentBase.cs:line 38
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c.<InvokeAsync>b__8_0(Object state)

Call stack

 	Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()	Unknown
>	Stl.Fusion.Blazor.dll!Stl.Fusion.Blazor.StatefulComponentBase..ctor.AnonymousMethod__1() Line 38	C#
 	Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.InvokeAsync.AnonymousMethod__8_0(object state)	Unknown
 	Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteSynchronously(System.Threading.Tasks.TaskCompletionSource<object> completion, System.Threading.SendOrPostCallback d, object state)	Unknown
 	System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state)	Unknown
 	Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteBackground(Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.WorkItem item)	Unknown
 	System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(System.Threading.Thread threadPoolThread, System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state)	Unknown
 	System.Private.CoreLib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot, System.Threading.Thread threadPoolThread)	Unknown
 	System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()	Unknown

Ill provide a pull request with a reproduction of the issue and a suggested fix/workaround.

Fusion client is trying to reconnect to a dead publisher for too long

The log part indicating this:

    Line 2:       R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-vdFui-KP5JB3VFI&clientId=R-da52z70rnU90aPED...
    Line 14:       R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-do-jCITwOmPKrjyx&clientId=R-da52z70rnU90aPED...
    Line 21:       R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-lYvW9eC0qQ7tE3bG&clientId=R-da52z70rnU90aPED...
    Line 23:       R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-vdFui-KP5JB3VFI&clientId=R-da52z70rnU90aPED...
    Line 35:       R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-do-jCITwOmPKrjyx&clientId=R-da52z70rnU90aPED...
    Line 42:       R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-lYvW9eC0qQ7tE3bG&clientId=R-da52z70rnU90aPED...
    Line 49:       R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-vdFui_-KP5JB3VFI&clientId=R-da52z70rnU90aPED...

Exception:

info: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-vdFui_-KP5JB3VFI&clientId=R-da52z70rnU90aPED...
fail: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: error
      System.Net.WebSockets.WebSocketException (0x80004005): The server returned status code '400' when status code '101' was expected.
         at System.Net.WebSockets.WebSocketHandle.ConnectAsync(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options)
         at Stl.Fusion.Client.WebSocketChannelProvider.CreateChannel(Symbol publisherId, CancellationToken cancellationToken)
fail: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: error
      System.Net.WebSockets.WebSocketException (0x80004005): The server returned status code '400' when status code '101' was expected.
         at System.Net.WebSockets.WebSocketHandle.ConnectAsync(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options)
         at Stl.Fusion.Client.WebSocketChannelProvider.CreateChannel(Symbol publisherId, CancellationToken cancellationToken)
info: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-do-jCITwOmPKrjyx&clientId=R-da52z70rnU90aPED...
fail: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: error
      System.Net.WebSockets.WebSocketException (0x80004005): The server returned status code '400' when status code '101' was expected.
         at System.Net.WebSockets.WebSocketHandle.ConnectAsync(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options)
         at Stl.Fusion.Client.WebSocketChannelProvider.CreateChannel(Symbol publisherId, CancellationToken cancellationToken)
info: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-lYvW9eC0qQ7tE3bG&clientId=R-da52z70rnU90aPED...
info: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-vdFui_-KP5JB3VFI&clientId=R-da52z70rnU90aPED...
fail: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: error
      System.Net.WebSockets.WebSocketException (0x80004005): The server returned status code '400' when status code '101' was expected.
         at System.Net.WebSockets.WebSocketHandle.ConnectAsync(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options)
         at Stl.Fusion.Client.WebSocketChannelProvider.CreateChannel(Symbol publisherId, CancellationToken cancellationToken)
fail: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: error
      System.Net.WebSockets.WebSocketException (0x80004005): The server returned status code '400' when status code '101' was expected.
         at System.Net.WebSockets.WebSocketHandle.ConnectAsync(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options)
         at Stl.Fusion.Client.WebSocketChannelProvider.CreateChannel(Symbol publisherId, CancellationToken cancellationToken)
info: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-do-jCITwOmPKrjyx&clientId=R-da52z70rnU90aPED...
fail: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: error
      System.Net.WebSockets.WebSocketException (0x80004005): The server returned status code '400' when status code '101' was expected.
         at System.Net.WebSockets.WebSocketHandle.ConnectAsync(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options)
         at Stl.Fusion.Client.WebSocketChannelProvider.CreateChannel(Symbol publisherId, CancellationToken cancellationToken)
info: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-lYvW9eC0qQ7tE3bG&clientId=R-da52z70rnU90aPED...
fail: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: error
      System.Net.WebSockets.WebSocketException (0x80004005): The server returned status code '400' when status code '101' was expected.
         at System.Net.WebSockets.WebSocketHandle.ConnectAsync(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options)
         at Stl.Fusion.Client.WebSocketChannelProvider.CreateChannel(Symbol publisherId, CancellationToken cancellationToken)
info: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: connecting to ws://localhost:9000/fusion/ws?publisherId=P-vdFui_-KP5JB3VFI&clientId=R-da52z70rnU90aPED...
fail: Stl.Fusion.Client.WebSocketChannelProvider[0]
      R-da52z70rnU90aPED: error
      System.Net.WebSockets.WebSocketException (0x80004005): The server returned status code '400' when status code '101' was expected.
         at System.Net.WebSockets.WebSocketHandle.ConnectAsync(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options)
         at Stl.Fusion.Client.WebSocketChannelProvider.CreateChannel(Symbol publisherId, CancellationToken cancellationToken)

Assistance/Improvement for Factories/Proxies using ActivatorUtilities

I'd like to know whether there is any better way of creating a Factory, that intercepts methods calls on that interface for passing these on to ActivatorUtilities, or if there is interest in this being integrated into Stl.Interception as a native supported pattern:

// should be singleton or scoped in MEDI
public sealed class ActivatorInterceptor(IServiceProvider services) : Interceptor
{
    private readonly ConcurrentDictionary<MethodInfo, ObjectFactory> _factories = [];
    private readonly IServiceProvider _services = services;

    public override TResult Intercept<TResult>(Invocation invocation)
    {
        var m = _factories.GetOrAdd(invocation.Method, CreateFactory<TResult>);
        return (TResult)m(_services, invocation.Arguments.ToArray());
    }

    private static ObjectFactory CreateFactory<T>(MethodInfo method) => CreateFactory<T>(method.GetParameters());

    private static ObjectFactory CreateFactory<T>(ParameterInfo[] parameters)
    {
        var arguments = new Type[parameters.Length];
        for (int i = 0; i < parameters.Length; i++)
        {
            arguments[i] = parameters[i].ParameterType;
        }

        return ActivatorUtilities.CreateFactory(typeof(T), arguments);
    }
}

public interface IFactory<TFactory> where TFactory : IFactory<TFactory>, IRequiresAsyncProxy;

Registering a factory:

public static IServiceCollection AddFactory<TFactory>(this IServiceCollection services) where TFactory : class, IFactory<TFactory>, IRequiresAsyncProxy
{
	services.AddSingleton(CreateFactory);
	return services;

	static TFactory CreateFactory(IServiceProvider services)
	{
		var interceptor = services.GetRequiredService<ActivatorInterceptor>();
		return services.ActivateProxy<TFactory>(interceptor);
	}
}

The interface is to make sure, that Stl.Generated-proxies are found correctly, and are available for consumption - this could probably be reduced to AddFactory<T> () where T : IRequiresAsyncProxy, but for me I implemented it this way for now.

And eventually could be used for something like:

public interface IViewModelFactory : IFactory<IViewModelFactory>, IRequiresFullProxy
{
    ConcreteViewModelTypeA ConcreteViewModelTypeA(int arg1, int arg2);
}

which returns an instance of

public record class ConcreteViewModelTypeA(
    int arg1,
    int arg2,
    ISomeService service,
    IOtherService otherService);

Regarding ArgumentList.ToArray() is probably not the best way to go about it (ObjectFactory is defined as delegate object? ObjectFactory(IServiceProvider, params object[] args), but I didn't see any other way as I figured ArgumentList.GetInvoker() isn't going to cut it, as it won't create the params-array when called.

Fix SafeJsonSerializer

https://github.com/servicetitan/Stl/blob/master/src/Stl/Serialization/SafeJsonNetSerializer.cs

Currently it "solves" two problems:

  1. Safe denationalization - the Verifier delegate is used on deserialization to tell if the root type is fine to deserialize, and this piece is totally ok to keep
  2. But overall, it doesn't allow to serialize everything b/c it serializes only the top level type name, so anything nested that needs a type (e.g. Exception) won't deserialize.

#2 works the way it does to actually address the issue w/ WASM: mscorlib is named differently there, so this serializer "fixes" this issue by normalizing its name - and it can do this nicely only b/c there is a single place it needs to fix (root type name).

The right fix, though, would be to fix mscorlib name everywhere during the serialization. Not sure how to do this w/ JSON.NET, but pretty sure it's possible. And if this is done, the serializer would be a complete fit for Stl.Fusion needs:

  • Safe deserialization will be used there on "update request" end (Publisher, etc.) - the types should be limited just to a few message types there, the rest should be banned.
  • And no-filter deserialization will be used on "update/invalidate receiver" end (i.e. WASM client), where it's totally safe to desalinize everything (it's still a browser-sandboxed .NET).

Stl.Interception gets trimmed away

Trying to get Stl.Interception TypedFactory to not get trimmed away in .NET 8.
image

Might need some C# 12 features to get working correctly: Interceptors, such that the proxy is known at compile time and isn't stripped away completely.

Assistance Needed: List Method Not Retrieving Values and Not Subscribing to Updates

Hi @alexyakunin 👋,

First off, a huge thanks for creating such a wonderful library! I’ve been navigating through Stl.Fusion and have stumbled upon a little hiccup that I’m hoping you could possibly assist me with.

Here’s a snippet of code where I’m experiencing the issue:

public interface ITripMonitor : IComputeService
{
    Task<int> AddOrUpdate(int tripId, CancellationToken cancellationToken = default);
    Task Remove(int tripId, CancellationToken cancellationToken = default);
    [ComputeMethod]
    Task<List<int>> List(CancellationToken cancellationToken = default);
    [ComputeMethod]
    Task<int> Count(CancellationToken cancellationToken = default);
}
public class TripMonitor : ITripMonitor
{
    private ImmutableList<int> _tripruning = ImmutableList<int>.Empty;
    private readonly IMutableState<int> _count;
    public TripMonitor(IStateFactory stateFactory)
        => _count = stateFactory.NewMutable<int>(0);
    public virtual Task<int> AddOrUpdate(int tripId, CancellationToken cancellationToken = default)
    {
        if (Computed.IsInvalidating())
            return null!;
        using var invalidating = Computed.Invalidate();
        _tripruning = _tripruning.RemoveAll(i => i == tripId).Add(tripId);
        _count.Value +=1;
        return Task.FromResult(tripId);
    }
    public virtual async Task Remove(int tripId, CancellationToken cancellationToken = default)
    {
        if (Computed.IsInvalidating())
            return;
        using var invalidating = Computed.Invalidate();
        _tripruning = _tripruning.RemoveAll(i => i == tripId);
        _count.Value -= 1;
    }
    public virtual Task<List<int>> List(CancellationToken cancellationToken = default)
    {
        return Task.FromResult(_tripruning.ToList());
    }
    public virtual async Task<int> Count(CancellationToken cancellationToken = default)
    {
        return await _count.Use(cancellationToken);
    }
}


//===============================//
@using Stl.CommandR.Commands;
@using Stl.Fusion.Blazor;
@using CleanArchitecture.Blazor.Application.Services;
@using Stl.Fusion.Extensions;
@using Stl.Fusion.UI;
@using Stl.Fusion;
@inherits ComputedStateComponent<string>
@inject ITripMonitor _tripMonitor
@inject UICommander UICommander
@inject IFusionTime _time
@{
    var state = State.ValueOrDefault;
    var error = State.Error;
}
@state
<br>
@error
<br>
<button class="btn btn-primary" @onclick="add">Add</button>
@code {
    protected override Task OnInitializedAsync() => State.Update().AsTask();
    protected override async Task<string> ComputeState(CancellationToken cancellationToken)
    {
        var result = await _tripMonitor.List(cancellationToken);
        var count = await _tripMonitor.Count(cancellationToken);
        return $"{count} / {string.Join(',',result.ToArray())} ";
    }
    async Task add()
    {
        var v = Random.Shared.Next(100);
        await UICommander.Run(LocalCommand.New(() => _tripMonitor.AddOrUpdate(v)));
    }
}
image

Currently, I’m scratching my head a bit 🤔 because the List method doesn’t seem to be fetching values, nor subscribing to updates as I initially anticipated. I’ve tried a few things, but seem to be spinning my wheels a bit.

If you could shed some light on what might be going awry and any pointers on how to rectify it, I’d be ever so grateful! 🙏

Thanks so much for your time and assistance!

Rewrite AsyncLockSet

Its current impl. "works" just because there are no robust tests:

  • It uses Dictionary<...> inside AsyncLocal - I somehow decided it's fine, but obviously it's not (what's inside AsyncLocal isn't automatically thread-safe).
  • There are no tests for it

Recently I rewrote regular AsyncLock (+ added tests), and it makes sense to either try implementing AsyncLockSet on top of it (con: potentially many AsyncLocals w/ DP-style recursive calls involving AsyncLockSet), or think of how to do this better.

Exception when using together with Moq 4.18.1

I suspect it may be caused by the fact that Moq 4.18.1 uses the new Castle.Core (DynamicProxy) version 5.0.0, while Stl uses 4.4.1 (the issue appears when upgrading Moq 4.16.1 to 4.18.1).

Code causing exception:

IServicesCollection Register(IServicesCollection services)
{
        return services
            .UseRegisterAttributeScanner()
            .RegisterFrom(assemblies)
            .Services;
}

Stacktrace:

System.Reflection.ReflectionTypeLoadException : Unable to load one or more of the requested types.
Could not load type 'Implementation' from assembly 'Stl.Fusion, Version=2.4.59.4351, Culture=neutral, PublicKeyToken=7c239217ec78b545' because the parent type is sealed.
Could not load type 'Implementation' from assembly 'Stl.Fusion, Version=2.4.59.4351, Culture=neutral, PublicKeyToken=7c239217ec78b545' because the parent type is sealed.
  ----> System.TypeLoadException : Could not load type 'Implementation' from assembly 'Stl.Fusion, Version=2.4.59.4351, Culture=neutral, PublicKeyToken=7c239217ec78b545' because the parent type is sealed.
  ----> System.TypeLoadException : Could not load type 'Implementation' from assembly 'Stl.Fusion, Version=2.4.59.4351, Culture=neutral, PublicKeyToken=7c239217ec78b545' because the parent type is sealed.
   at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
   at System.Reflection.RuntimeModule.GetTypes()
   at Stl.Extensibility.MatchingTypeFinder.<>c.<.ctor>b__8_0(Assembly a)
   at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.MoveNext()
   at Stl.Extensibility.MatchingTypeFinder..ctor(IEnumerable`1 candidates)
   at Stl.Extensibility.MatchingTypeFinder..ctor(IEnumerable`1 assemblies)
   at Stl.Extensibility.MatchingTypeFinder..ctor()
   at Stl.Fusion.Interception.ArgumentHandlerProvider.Options..ctor()
   at Stl.Fusion.FusionBuilder..ctor(IServiceCollection services)
   at Stl.Fusion.ServiceCollectionExt.AddFusion(IServiceCollection services)
   at Stl.Fusion.RegisterComputeServiceAttribute.Register(IServiceCollection services, Type implementationType)
   at Stl.RegisterAttributes.RegisterAttributeScanner.Register(IEnumerable`1 services, Boolean filterByScope, Boolean filterByType)
   at Stl.RegisterAttributes.RegisterAttributeScanner.RegisterFrom(Assembly[] assemblies)

A DateTime value was incorrectly passed to the server in Korean.

in Korean,

The DateTime is sent to the server like this:

2022. 7. 29. 오전 12:00:00

image

Looks like I'll have to send it like this:

2022-07-29T00:00:00

This value is the log delivered to 'HelloBlazorHybrid' 'Fetch Data' of 'Stl.Fusion.Samples' and can be checked:

info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished HTTP/2 GET https://localhost:7464/api/weatherForecast/getForecast?startDate=2022.+7.+29.+%EC%98%A4%EC%A0%84+12%3A00%3A00 - - - 400 - application/problem+json;+charset=utf-8 0.4104ms

It looks like it should be delivered like this:

https://localhost:7464/api/weatherForecast/getForecast?startDate=2022-07-29T00:00:00

image

Possible renames

Hi, I'm thinking of a few renames:

Renames in Stl.Fusion:

  • IComputed -> IDependency
  • IComputeService -> IFusionService, [ComputeMethod] -> [FusionMethod]
  • IReplicaService -> IFusionClient

Overall, above changes will replace "computed" and "replica" keywords that have special meaning in Fusion with seemingly a bit more clear variants.

All related types will be renamed to match these changes.

And similar renames in Stl:

  • ServiceAttributeBase -> RegisterAttributeBase, all of its descendants will get Register prefix
  • MatchingTypeFinder -> BehaviorFinder

Do you think it makes sense to do this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.