Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Proposal: Use Castle Proxies #9

Open
smfields opened this issue Mar 18, 2023 · 3 comments
Open

Feature Proposal: Use Castle Proxies #9

smfields opened this issue Mar 18, 2023 · 3 comments

Comments

@smfields
Copy link
Contributor

Proposal

I'd like to propose moving this library to using Castle DynamicProxy to generate the client-side VM proxies over the custom-built solution it currently uses for a few reasons:

  1. DynamicProxies support intercepting async methods, including async methods that return a value. This is something that has been brought up previously on Async Submit and File Uploads with Dotnetify Blazor? #6, and as I outlined on that issue, not having support for this can cause a number of issues.

#6 (comment)

Is supporting Task return types something that's planned to be added? This limitation causes issues with sharing the view model interface between the client and the server (one of the big benefits of using Blazor).

Currently you can't have a Task return type in the interface because of the above error. You can't implement the method in the server side view model with a Task return type if the interface returns void, meaning that you have to change the return type in the interface to void. While async methods with void return types are technically valid, they cause a whole slew of issues when it comes to things like exceptions.

So at the moment the only real valid option is to have two interfaces, one for the client-side to proxy that has a void return type, and one for the server-side view model to implement that has a Task return type.

  1. Castle DynamicProxy is a very well established and widely used library for generating proxy objects, and has been used in numerous popular libraries such as Entity Framework Core, Moq, NSubstitute, and many more. Moving to such a stable implementation should make it easier to focus on the goals of this project rather than needing to implement a bunch of proxy generation code.

I went ahead and put together a sample of the changes necessary. There's actually two different versions, and you can see more information about them below.

Shared Implementation

Both implementations depend on the Castle.Core library which provides the ability to create dynamic proxies. More specifically we're creating a type of proxy known as an Interface proxy without target, which just means there is no underlying target object that is provided, and instead we depend on something called interceptors to implement the required functionality.

You can see the implementation of the interceptor here. It functions very similarly to what the BaseObject used to be, implementing handlers for property getters and setters, as well as method calls.

Newtonsoft-based Version

In this version we maintain the dependency on Newtonsoft, and use a feature of Newtonsoft called a CustomCreationConverter to implement a converter that will deserialize into a new proxy object. You can see the implementation here.

The downside here is that there's currently a bug in Newtonsoft that prevents this from working correctly. I've got a PR open there as well to fix the bug.

System.Text.Json-based Version

In this version we remove dotNetify's dependency on Newtonsoft, and switch to the built-in .NET serializer System.Text.Json. This approach uses a feature of System.Text.Json called a JsonTypeInfoResolver to do the deserialization into the proxy object. You can see the implementation of it here.

Removing the dependency on System.Text.Json has its pros and cons:

Pros:

  • Fewer dependencies (System.Text.Json is built-in)
  • Smaller assembly size (important for WebAssembly)

Cons:

  • Requires an upgrade to .NET 7 to get access to the JsonTypeInfoResolver class

Let me know your thoughts. I'm happy to contribute in whatever way would be useful. The samples I've provided are both fully functioning and tested, so I'd be happy to open a PR if this is something you'd like to do.

@dsuryd
Copy link
Owner

dsuryd commented Mar 18, 2023

I think this is great stuff, thanks! The only concern I have with using Castle is the additional DLL to download. If the type proxy can be made to support async methods, it will be more size-optimal.

@smfields
Copy link
Contributor Author

I think this is great stuff, thanks! The only concern I have with using Castle is the additional DLL to download. If the type proxy can be made to support async methods, it will be more size-optimal.

Yeah that's always important with WebAssembly. The only thing I can say there is that the Castle.Core assembly is less than half the size of something like Newtonsoft.

@dsuryd
Copy link
Owner

dsuryd commented Mar 18, 2023

I made updates to TypeProxy.cs to accept async methods. Let me know if that works for you. I'm still open to using Castle, but I'd like to see more use cases that make it hard to continue with this custom solution.

Regarding the serializer lib, I think both can be supported by testing the target framework. Use System.Text.JSON if .NET >= 7.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants