Giter Club home page Giter Club logo

csharp-res's Introduction

Resgate logo

RES Service for .NET
Synchronize Your Clients

License NuGet Build Status


Library for .NET used to create next generation REST, real time, and RPC APIs, where all your reactive web clients are synchronized seamlessly through Resgate.

Visit Resgate.io for more information.

As easy as

ResService service = new ResService("example");
service.AddHandler("model", new DynamicHandler()
    .Get(r => r.Model(new {
        message = "Hello, World!"
    }))
    .Access(r => r.AccessGranted()));
service.Serve("nats://127.0.0.1:4222");

Examples

Example Description
Hello World Smallest of services serving a static message.
Edit Text Single text field that is updated in real time.
Book Collection List of book titles & authors that can be edited by many.
Search Make live queries against a large customer database.

Basic usage

Create a new service

ResService service = new ResService("myservice");

Define a handler class for a model resource

[ResourcePattern("mymodel")]
class MyModelHandler : BaseHandler
{
    private readonly object model = new
    {
        message = "Hello, .NET World!"
    };

    public void Get(IModelRequest request)
    {
        request.Model(model);
    }
}

Define a handler class for a collection resource

[ResourcePattern("mycollection")]
class MyCollectionHandler : BaseHandler
{
    private readonly object[] collection = new object[]{
        "first", "second", "third"
    };

    public void Get(ICollectionRequest request)
    {
        request.Collection(collection);
    }
}

Define methods on a handler class

[ResourcePattern("math")]
class MyResourceHandler : BaseHandler
{
    [CallMethod("double")]
    public void Double(ICallRequest r)
    {
        r.Ok(2 * (double)r.Params["value"]);
    }
}

Add/register handler for a resource

service.AddHandler(new MyResourceHandler());

Add handlers for parameterized resources

service.AddHandler("article.$id", new DynamicHandler()
    .Access(r => r.AccessGranted())
    .ModelGet(r =>
    {
        if (DB.TryGetArticle(r.PathParams["id"], out Article article))
            r.Model(article);
        else
            r.NotFound();
    }));

Send change event on model update

A change event will update the model on all subscribing clients.

MyModel mymodel = new MyModel { Name = "foo" };
MyModel mymodel = new MyModel { Name = "foo" };
service.With("example.mymodel", resource =>
{
    mymodel.Name = "bar";
    resource.ChangeEvent(new Dictionary<string, object> {
        { "name", "bar" }
    });
});

Send add event on collection update:

An add event will update the collection for all subscribing clients.

var mycollection = new List<string> { "first", "second" };
service.With("example.mycollection", resource =>
{
    resource.AddEvent("third", mycollection.Count);
    mycollection.Add("third");
});

Add handlers for authentication

service.AddHandler("myauth", new DynamicHandler()
    .AuthMethod("login", r =>
    {
        if ((string)r.Params["password"] == "mysecret")
        {
            r.TokenEvent(new { user = "admin" });
            r.Ok();
        }
        else
        {
            r.InvalidParams("Wrong password");
        }
    }));

Add handlers for access control (with wildcard ">")

service.AddHandler(">", new DynamicHandler()
    .Access(r =>
    {
        if (r.Token != null && (string)r.Token["user"] == "admin")
            r.AccessGranted();
        else
            r.AccessDenied();
    }));

Add async handler

service.AddHandler("store.users", new DynamicHandler()
    .Get(async r =>
    {
        var users = await DB.QueryAsync("SELECT id FROM users");
        r.Collection(users.Select(u => new Ref("store.user." + u.Id)));
    }));

Start service

service.Serve("nats://127.0.0.1:4222");

Credits

Inspiration and support in development from github.com/novagen, who wrote an initial .NET library for Resgate.

Contributing

The .NET library is still under development, but the API is mostly settled. Any feedback on the library API or its implementation is highly appreciated!

Once the API is fully settled, the package will be moved to the resgateio GitHub organization.

If you find any issues, feel free to report them as an Issue.

csharp-res's People

Contributors

jirenius avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar

csharp-res's Issues

Travis support

Issue

Travis CI should be added to run CI builds on commits and PRs.

Add ResService.WithGroup

Issue

It should be possible to run a callback on a group thread/task without having a specific ResourceContext or resource ID.

Also, IResourceContext should have a Group property with the contexts group ID, that defaults to the resource name in case a group is not set.

Example

service.WithGroup("books", () => {
     // Callback is called on the thread/task of "books"
});

Notes

  • The following WithGroup overloads should exist:
    WithGroup(string group, Action callback)
    WithGroup(string group, Action<ResService> callback)
    This allows usage of callback methods that don't naturally have access to the service.
  • WithGroup should be used instead of overloading With, to avoid ambiguity when using lambda expressions:
    service.With("test.model", resource => {});
    service.WithGroup("mygroup", service => {});

AddHandler on root

Issue

It should be allowed to add a handler to the root of a router, if the router has a pattern set.

Example

ResService service = new ResService("example");
service.AddHandler(new DynamicHandler().SetCallMethod("foo", r = r.Ok()));

SetOwnedResources does not subscribe to call methods

Issue

When setting owned resourced with ResService.SetOwnedResources, where the resource patterns does not contain full wildcards (>), the auth and call request NATS subjects will not be correctly subscribed to.

Event handlers

Issue

ResService should have the following events:

  • Serving - On serving after the initial system.reset is sent.
  • Disconnected - On disconnect from NATS.
  • Reconnected - On reconnect to NATS, after system.reset is sent.
  • Stopped - On service being stopped.
  • Error - On any error that causes an Error log.

Group support

Issue

Multiple resources might want to be handled on the same worker thread to avoid having to add additional synchronization.

This can be handled by adding the concept of Group, a pattern string that works as the ID for the worker thread. Multiple resources with the same Group will be running on the same worker thread.

Note

The Group pattern should be allowed to contain placeholder tags. Eg.:
group.${contextId}

These tags must also exist as placeholder tags in the resource pattern used when calling AddHandler.

Example

Let's say we have a filtered collection, users.admins, containing a list of model references to user models with the role of admin. This collection is dependent upon the content of the user models, and may change whenever any model role is updated.

To simplify synchronization, the users.admins collection and all the user models may be handled serialized by the same worker thread.

QueryEvent with async callback fails

Issue

When sending a query event with an async callback, the library will instantly send an empty event response on each query request without awaiting the completion of the callback.

Add .NETStandard 2.0 target

Issue

The .NETStandard 1.6 target will explicitly require NETStandard.Library >= 1.6.1 . From .NETStandard 2.0 and onward, this will not be needed, and will reduce the number of dependencies required when installing the NuGet package.

Solution

Add .NETStandard2.0 as a target to the project.

Add OnRegister method to IAsyncHandler

Issue

The IAsyncHandler interface should provide means for ResService to pass its own instance and the full pattern string back to the handler once it has been registered to the service.

Solution

Add a new method to IAsyncHandler:

/// <summary>
/// Called when the handler is registered to a service.
/// </summary>
/// <param name="service">Service which the handler is registered to.</param>
/// <param name="pattern">Full resource id pattern being handled.</param>
void OnRegister(ResService service, String pattern);

Improve ILogger

Issue

The current ILogger interface requires the service library to generate log strings even if they are not to be logged. This produces unnecessary garbage which can be avoided.

In addition, debug logging should be added too ILogger. The default ConsoleLogger implementation should have Debug deactivated by default.
Debug logging should, for starters, include logging the cleanup steps taken.

Solution

The interface should be changed to accept variadic parameters, similar to those of String.Format.

Compatibility

Previously implementation of the ILogger interface will need to be updated.

Complete basic feature set

Issue

The csharp-res library lacks basic features found in more complete libraries, such as the go-res package.

Scope

The following features should be added or updated

  • Query event
  • Timeout pre-response support
  • Apply handlers (to allow middlewares)
  • Value retrieval from Get handlers
  • Router mounting
  • Refactored IResourceHandler interface (to allow decorators)
  • Abstract ResourceHandler class
  • ILogger interface

RES Protocol v1.2.0 support

Issue

Add support for the changes made in RES Service Protocol v1.2.0

  • Add Resource response to call and auth requests
  • Add Resource response to query events.
  • Add ProtocolVersion to ResService
  • Mark New method and INewRequest as Obsolete.
  • Update Search query example to use Resource response to query events.

Exception when registering call method named new

Issue

Using DynamicHandler or_BaseHandler_ to register a call method with the name "new" will result in the exception:

System.ArgumentException: Must use SetNew to register handler for new call requests

Solution

This is due to a deprecated validation used to enforce correct behavior prior to v0.4.2 and RES protocol v1.2.0. The validation should be removed and any example updated.

Unit tests

Issue

Unit tests should be added to find existing bugs and stop new bugs from enter the code.

Notes

The testing suite from the go-res package would be a good reference for the scope of the tests.

In addition, the testing suite from timerqueue can be used as reference for the TimerQueue class.

Handler event listeners

Issue

When creating resources whose content is dependent on other resources (eg. A collection of model references sorted by a property value in the referenced models), there must be a way to listen for events on the dependent resource.

Or in other terms: a handler should have a way to listen to events triggered by other handlers.

Notes

Registering event listeners

These listeners should both be possible to make from the ResService instance:

var service = new ResService("example");
service.AddHandler("book.$id", new BookModelHandler());
var h = new BooksCollectionHandler();
service.AddHandler("books", h);
// Maybe like this
service.AddEventListener("book.$id", h.OnBookEvent);

Or by using attributes to make the handler more self contained:

class BooksCollectionHandler: CollectionHandler {

    [EventListener("book.$id")]
    public void OnBookEvent(IResourceContext r, object ev)
    {
        if (ev is IChangeEvent change)
        {
            // Do stuff with change
        }
    }
}

Worker task

The handler will be called from the same worker task of the resource triggering the event, and not the resource listening for events.

Resolving EventListeners

The EventListeners will be resolved when ResService.Serve is called. This makes it possible to add listeners for handlers before they are registered.

Race issue if QueryEvent fails to subscribe

Issue

When calling ResourceContext.QueryEvent(callback), if the call fails to create a subscription to the temporary inbox, the async callback method will be called without waiting for the result before the call to QueryEvent returns.

This can in rare cases cause a race condition.

Solution

In case of failed subscription, the callback should be queued on the same resource worker Task from which QueryEvent was called.

JSON serializer settings

Issue

The default JSON serializer settings used by ResService may not be preferred. It should be possible to set a custom setting to be used instead.

Solution

Add a method to set custom JSON serializer settings:

/// <summary>
/// Sets the settings used with JSON serialization.
/// </summary>
/// <param name="settings">JSON serializer settings.</param>
/// <returns>The ResService instance.</returns>
public ResService SetSerializerSettings(JsonSerializerSettings settings)

Multi target frameworks

Issue

The NuGet package should have multi target support for the following two targets:

  • .NETStandard 1.6
  • .NETFramework 4.5

Support new requests

Issue

New call request is currently handled by the Call handler, but should have its own handler to make it easier to comply with the RES service protocol specification.

Blazer ?

Wondering if you have explored blazer wasm aspects ?

NATS Conn events not listened to

Issue

When calling ResService.Serve(IConnection conn), no listeners are added to the provided NATS conn object.

This will cause ResService to fail responding to reconnects and other NATS events.

Reproduction

Options opts = ConnectionFactory.GetDefaultOptions();
opts.Url = url;
IConnection conn = new ConnectionFactory().CreateConnection(opts);
var service = new ResService("example");
service.AddHandler("model", new DynamicHandler().SetGet(r => r.Model(new { foo = "bar" })));
service.Serve(conn);
// Restart NATS to force a reconnect.
// ResService should react with sending a system.reset event, but doesn't.

Notes

Listeners are currently only added when ResService creates its own IConnection object when calling ResService.Serve(string url).

BaseHandler call method attribute

Issue

The BaseHandler should have a method attribute to look for named methods.

Example

class MyHandler : BaseHandler
{
    [CallMethod("add")]
    public void Add(ICallRequest request)
    {
        request.Ok(2 * (double)request.Params["value"]);
    }

    [AuthMethod("login")]
    public void Login(IAuthRequest request)
    {
        /* Perform login */
    }
}

It may additionally look up named method based on their signature. In such a case, the name of the call method will be the same as the class method but with first-letter-lowercase.

Example

class MyHandler : BaseHandler
{
    // Add matches the signature of a call method.
    // It will be automatically added with the method name "add".
    public void Add(ICallRequest request)
    {
        request.Ok(2 * (double)request.Params["value"]);
    }
}

Note

The methods void Call(ICallRequest request) and void Auth(IAuthRequest request) should be excluded from reflection as they are part of the IResourceHandler interface.

Discard events prior to system.reset

Issue

Events attempted to be sent by the service prior to the initial system.reset is sent, should be discarded with a Log.Trace message.

This is to prevent events being sent for resources which might be stale in Resgate's cache.

Notes

This also applies to events attempted to be sent during a reconnect.

Basic usage in README

Issue

The README should contain basic usage snippets, to help understand how to use the library.

Make IRequestHandler asynchronous

Issue

Currently, the IResourceHandler has synchronous methods, but there is a need to allow asynchronous methods to handle the requests.

Example:

var h = new DynamicHandler()
    .SetGet(async req => {
        var model = await MyDB.GetModel(req.ResourceName);
        req.Model(model);
    });

Suggested solutions

Alternative 1

Update IResourceHandler to have all methods return Task.

This would be a backwards incompatible change, and would not allow for any synchronous handling which might cause a slight overhead (?).

Alternative 2

Create a new IResourceHandlerAsync interface with the same methods but returning Task.

This would lead to a set of similar classes, such as IBaseHandlerAsync, IModelHandlerAsync, ICollectionHandlerAsync, and IDynamicHandlerAsync. Also, it would not allow mixing synchronous and asynchronous handlers.

Alternative 3

Add a duplicate set of methods to IResourceHandler with for Async methods, such as Task GetAsync(IGetRequest req).

This alternative requires a way for ResService to determine whether to use the Get or GetAsync method when handling a get request.

Notes

This issue might also include the scope of the making the With and WithGroup methods return Tasks:

Task t = service.With("myservice.mymodel", r => {
    r.Event("custom");
});

Implement IDisposable

Issue

ResService should implement IDisposable.

Notes

All necessary steps are performed in ResService.cleanup(), so the implementation should just be:

 public void Dispose()
{
    cleanup();
}

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.