The most requested feature since our last release was to expand our last releases support for Server Sent Events with both a scale-out Redis ServerEvents back-end for use in load-balanced App Servers scenarios as well as a typed C# ServerEvents Client - we're happy to announce we've been able to deliver both features in this release!
Major features in this release​
- Server Events
- Redis
- AppSettings
- Metadata Pages
- Authentication
- OrmLite
- Text
- Community
Redis ServerEvents​
One limitation the default MemoryServerEvents
implementation has is being limited for use within a single App Server where all client connections are maintained. This is no longer a limitation with the new Redis ServerEvents back-end which utilizes a distributed redis-server back-end to provide a scale-out option capable of serving fan-out/load-balanced App Servers. If you're familiar with SignalR, this is akin to SignalR's scaleout with Redis back-end.
RedisServerEvents
is a drop-in replacement for the built-in MemoryServerEvents
that's effectively a transparent implementation detail, invisible to Server or Client API's where both implementations even share the same integration Tests.
Enabling RedisServer Events​
As a drop-in replacement it can easily be configured with just a few lines of code, as seen in the updated Chat App which can run on either Memory or Redis ServerEvents providers:
var redisHost = AppSettings.GetString("RedisHost");
if (redisHost != null)
{
container.Register<IRedisClientsManager>(new PooledRedisClientManager(redisHost));
container.Register<IServerEvents>(c =>
new RedisServerEvents(c.Resolve<IRedisClientsManager>()));
container.Resolve<IServerEvents>().Start();
}
The above configuration will use Redis ServerEvents if there's a RedisHost
appSetting in Chat's Web.config:
<add key="RedisHost" value="localhost:6379" />
Cross-platform Memory and Redis ServerEvent Enabled Chat.exe​
To showcase Redis ServerEvents in action, we've prepared a stand-alone ServiceStack.Gap version of Chat compiled down into a single Chat.exe that can run on either Windows and OSX with Mono which can be downloaded from:
Chat.zip (1.2MB)​
As Chat only runs on 2 back-end Services, it fits well within ServiceStack's Free Quota's which can be further customized and enhanced without a commercial license.
Running Chat.exe without any arguments will run Chat using the default Memory ServerEvents. This can be changed to use Redis ServerEvents by un-commenting this line in appsettings.txt:
#redis localhost
This will require a redis-server running on localhost
. If you don't have redis yet, download redis-server for Windows.
Alternatively you can specify which port to run Chat on and change it to use Redis ServerEvents by specifying the redis instance it should connect to on the command-line with:
Chat.exe /port=1337 /redis=localhost
Also included in Chat.zip
are test-fanout-redis-events.bat and equivalent test-fanout-redis-events.sh helper scripts for spawning multiple versions of Chat.exe on different ports (and backgrounds) for Windows or OSX, showing how multiple clients are able to send messages to each other via Redis whilst being subscribed to different HTTP Servers:
START Chat.exe /port=1337 /redis=localhost
START Chat.exe /port=2337 /redis=localhost /background=http://bit.ly/1oQqhtm
START Chat.exe /port=3337 /redis=localhost /background=http://bit.ly/1yIJOBH
This script was used to create the animated gif above to launch 3 self-hosting instances of Chat.exe running on different ports, all connected to each other via Redis. This enables some interesting peer-to-peer scenarios where users are able to run a network of (CPU/resource isolated) decentralized stand-alone HTTP Servers on their local machines, but can still communicate with each other via redis.
C# ServerEvents Client​
Like ServiceStack's other C# Service Clients, the new ServerEventsClient
is a portable library contained in the ServiceStack.Client
NuGet package:
PM> Install-Package ServiceStack.Client
And like the Service Clients it requires the BaseUri
of your ServiceStack instance as well as an optional channel
for the client to subscribe to:
var client = new ServerEventsClient("http://chat.netcore.io", channel:"home");
Managed Connection​
The C# ServerEvent Client is a managed .NET client with feature parity with the ServiceStack's JavaScript client that auto-reconnects when a connection is lost, sends periodic heartbeats to maintain an active subscription as well as auto-unregistering once the client stops listening for messages, or gets disposed.
Handling Server Events​
Unlike other C# clients, the ServerEvents Client is mainly reactive in that it's primarily waiting for Server Events to be initiated from a remote server instead of the typical scenario in which requests are initiated by clients. To maximize utility, there are a number of different API's to receive and process messages:
Assigning Callback Handlers​
One way to receive messages (useful in long-running clients) is to assign handlers for each of the different events that are fired. This example shows how to capture all the different events a Client can receive:
ServerEventConnect connectMsg = null;
var msgs = new List<ServerEventMessage>();
var commands = new List<ServerEventMessage>();
var errors = new List<Exception>();
var client = new ServerEventsClient(baseUri) {
OnConnect = e => connectMsg = e,
OnCommand = commands.Add,
OnMessage = msgs.Add,
OnException = errors.Add,
}.Start();
Once the Client is configured, calling Start()
will start listening for messages and calling Stop()
or Dispose()
will cancel the background HTTP connection and stop it listening for server events.
Customizing Metadata sent to clients​
As ServerEvents have deep integration with the rest of ServiceStack we're able to offer Typed Messages containing the users UserAuthId
, DisplayName
and ProfileUrl
of the users avatar when it's available. The typed messages also offer an extensible Dictionary<string,string> Meta
collection for maintaining custom metadata that can be sent to clients by appending to them in the ServerEventsFeature hooks, which can be defined when registering ServerEventsFeature
:
Plugins.Add(new ServerEventsFeature {
// private Connect args
OnConnect = (subscription,httpReq) => AppendTo(subscription.Meta),
// public Join/Leave args
OnCreated = (subscription,httpReq) => AppendTo(subscription.Meta),
})
Using C# Async/Await friendly API's​
Depending on your use-case, if you only want to use the ServerEvent Client for a short-time to listen for predictable responses (i.e. waiting for a Server callback on a pending request) you can alternatively use the Task-based API's letting you to participate in C# async/await workflows:
var client = new ServerEventsClient(baseUri, channel="Home");
// Wait to receive onConnect event
ServerEventConnect connectMsg = await client.Connect();
// Wait to receive onJoin command event
ServerEventCommand joinMsg = await client.WaitForNextCommand();
// Hold a future task to get notified once a msg has been received
Task<ServerEventMessage> msgTask = client1.WaitForNextMessage();
// Send a Web Service Request using the built-in JsonServiceClient
client.ServiceClient.Post(new PostChatToChannel {
Channel = client.Channel, // The channel we're listening on
From = client.SubscriptionId, // SubscriptionId Populated after Connect()
Message = "Hello, World!",
});
// Wait till we receive the chat Msg event we sent earlier
ServerEventMessage msg = await msgTask;
The above example showcases the 3 Task-based API's available:
Connect()
wait till receiving confirmation of a successful event subscriptionWaitForNextCommand()
wait for the nextonJoin
oronLeave
subscription eventsWaitForNextMessage()
wait for the next message published to the channel
The ServiceClient
property lets you access a JsonServiceClient
that's pre-configured with the clients BaseUri
so that is primed for Sending Web Service Requests with.
After the ServerEvent Client has connected, the ConnectionInfo
property is populated with the typed ServerEventConnect
response.
Message Event Handlers​
The above examples show generic API's for receiving any type of message, but just like in the JavaScript client, more fine-grained API's are available for handling specific message types.
The Handlers
dictionary is akin to the JavaScript Client's Global Event Handlers which specify lambda's to be executed when messages are sent with the cmd.*
selector:
client.Handlers["chat"] = (client, msg) => {
var chatMsg = msg.Json.FromJson<ChatMessage>(); //Deserialize JSON string to typed DTO
"Received '{0}' from '{1}'".Print(chatMsg.Message, chatMsg.FromName);
};
Roughly translates to the equivalent JavaScript below:
$(source).handleServerEvents({
handlers: {
chat: function (msg, event) {
console.log("Received " + msg.message + " from " + msg.fromName);
}
}
});
Where both methods handle the ChatMessage
sent with the cmd.chat
selector.
Named Receivers​
Whilst handlers provide a light way to handle loose-typed messages, there's a more structured and typed option that works similar to ServiceStack's IService
classes but are used to instead handle typed Server Event Messages.
To be able to handle messages with your own classes, get them to implement the IReceiver
empty marker interface:
public interface IReceiver
{
void NoSuchMethod(string selector, object message);
}
Whilst primarily a marker interface, IReceiver
does include a NoSuchMethod
API to be able to handle messages sent with a unknown selector target that doesn't match any defined method or property.
Named Receivers are equivalent to Receivers in the JavaScript client which can be assigned to handle all messages sent to a receiver with the selector format:
{receiver}.{target}
A Named Receiver can be registered with the API below:
client.RegisterNamedReceiver<TestNamedReceiver>("test");
Which will forward all messages with a test.*
selector to an instance of the TestNamedReceiver
Type
public class TestNamedReceiver : ServerEventReceiver
{
public void FooMethod(CustomType request) {} // void return type
public CustomType BarMethod(CustomType request)
{
return request; // works with any return type, which are ignored
}
public CustomType BazSetter { get; set; } // Auto populate properties
public override void NoSuchMethod(string selector, object message)
{
var msg = (ServerEventMessage)message;
var nonExistentMethodCustomType = msg.Json.FromJson<CustomType>();
}
}
This is roughly equivalent to the following JavaScript code:
$(source).handleServerEvents({
receivers: {
test: {
FooMethod: function (msg, event) { ... },
BarMethod: function (msg, event) { ... },
BazSetter: null,
}
}
});
The ServerEventReceiver is a convenient base class that in addition to implementing
IReceiver
interface, gets injected with theClient
as well as additional context about the raw message available inbase.Request
.
Unknown Message Handling​
One difference in the JavaScript client is that messages with unknown targets are assigned as properties on the test
receiver, e.g test.QuxTarget = {..}
.
Sending messages to Named Receivers​
Once registered, an instance of TestNamedReceiver
will process messages sent with a test.*
selector. The example below shows how to send a DTO to each of TestNamedReceiver
defined methods and properties:
public class MyEventServices : Service
{
public IServerEvents ServerEvents { get; set; }
public void Any(CustomType request)
{
ServerEvents.NotifyChannel("home", "test.FooMethod", request);
ServerEvents.NotifyChannel("home", "test.BarMethod", request);
ServerEvents.NotifyChannel("home", "test.BazSetter", request);
ServerEvents.NotifyChannel("home", "test.QuxTarget", request);
}
}
Life-cycle of Receivers​
Similar to Services in ServiceStack, each message is processed with an instance of the Receiver that's resolved from ServerEventsClient.Resolver
which by default uses the NewInstanceResolver to execute messages using a new instance of the Receiver Type:
public class NewInstanceResolver : IResolver
{
public T TryResolve<T>()
{
return typeof(T).CreateInstance<T>();
}
}
This can be changed to re-use the same instance by assigning a SingletonInstanceResolver instead:
public class SingletonInstanceResolver : IResolver
{
ConcurrentDictionary<Type, object> Cache = new ConcurrentDictionary<Type, object>();
public T TryResolve<T>()
{
return (T)Cache.GetOrAdd(typeof(T), type => type.CreateInstance<T>());
}
}
client.Resolver = new SingletonInstanceResolver();
We can also have it resolve instances from your preferred IOC. Here's an example showing how to register all Receiver Types, auto-wire them with any custom dependencies, and instruct the client to resolve instances from our IOC:
// Register all Receivers:
client.RegisterNamedReceiver<TestNamedReceiver>("test");
...
// Register all dependencies used in a new Funq.Container:
var container = new Container();
container.RegisterAs<Dependency, IDependency>();
// Go through an auto-wire all Registered Receiver Types with Funq:
container.RegisterAutoWiredTypes(client.ReceiverTypes);
// Change the client to resolve receivers from the new Funq Container:
client.Resolver = container;
We can assign Funq.Container
directly as it already implements the IResolver interface, whilst you can re-use the existing IOC Container Adapters to enable support for other IOCs.
The Global Receiver​
Whilst Named Receivers are used to handle messages sent to a specific namespaced selector, the client also supports registering a Global Receiver for handling messages sent with the special cmd.*
selector.
Handling Messages with the Default Selector​
All IServerEvents
Notify API's inlcudes overloads for sending messages without a selector that by convention will take the format cmd.{TypeName}
.
These events can be handled with a Global Receiver based on Message type, e.g:
public class GlobalReceiver : ServerEventReceiver
{
public SetterType AnyNamedProperty { get; set; }
public void AnyNamedMethod(CustomType request)
{
...
}
}
client.RegisterReceiver<GlobalReceiver>();
Which will be called when messages are sent without a selector, e.g:
public class MyServices : Service
{
public IServerEvents ServerEvents { get; set; }
public void Any(Request request)
{
ServerEvents.NotifyChannel("home", new CustomType { ... });
ServerEvents.NotifyChannel("home", new SetterType { ... });
}
}
As Global Receivers handle other messages sent with the cmd.*
selector and can be re-used as a named receiver, we can define a single class to handle all the different custom messages sent in chat.netcore.io App, E.g:
cmd.chat Hi
cmd.announce This is your captain speaking...
cmd.toggle#channels
css.background-image url(https://servicestack.net/img/bg.jpg)
...
The above messages can all be handled with the Receiver below:
public class JavaScriptReceiver : ServerEventReceiver
{
public void Chat(ChatMessage message) { ... }
public void Announce(string message) { ... }
public void Toggle(string message) { ... }
public void BackgroundImage(string cssRule) { ... }
}
client.RegisterNamedReceiver<JavaScriptReceiver>();
client.RegisterNamedReceiver<JavaScriptReceiver>("css");
As seen above the target names are case-insensitive and -
are collapsed to cater for JavaScript/CSS naming conventions.
ServiceStack.Redis​
Redis Pub/Sub Server​
To power RedisServerEvents we've extracted the managed Pub/Sub long-running message-loop originally built for Redis MQ and encapsulated it into a re-usable class that can be used independently for handling messages published to specific Redis Pub/Sub channels.
RedisPubSubServer
processes messages in a managed background thread that automatically reconnects when the redis-server connection fails and works like an independent background Service that can be stopped and started on command.
The public API is captured in the IRedisPubSubServer interface:
public interface IRedisPubSubServer : IDisposable
{
IRedisClientsManager ClientsManager { get; }
// What Channels it's subscribed to
string[] Channels { get; }
// Run once on initial StartUp
Action OnInit { get; set; }
// Called each time a new Connection is Started
Action OnStart { get; set; }
// Invoked when Connection is broken or Stopped
Action OnStop { get; set; }
// Invoked after Dispose()
Action OnDispose { get; set; }
// Fired when each message is received
Action<string, string> OnMessage { get; set; }
// Fired after successfully subscribing to the specified channels
Action<string> OnUnSubscribe { get; set; }
// Called when an exception occurs
Action<Exception> OnError { get; set; }
// Called before attempting to Failover to a new redis master
Action<IRedisPubSubServer> OnFailover { get; set; }
int? KeepAliveRetryAfterMs { get; set; }
// The Current Time for RedisServer
DateTime CurrentServerTime { get; }
// Current Status: Starting, Started, Stopping, Stopped, Disposed
string GetStatus();
// Different life-cycle stats
string GetStatsDescription();
// Subscribe to specified Channels and listening for new messages
IRedisPubSubServer Start();
// Close active Connection and stop running background thread
void Stop();
// Stop than Start
void Restart();
}
To use RedisPubSubServer
, initialize it with the channels you want to subscribe to and assign handlers for each of the events you want to handle. At a minimum you'll want to handle OnMessage
:
var clientsManager = new PooledRedisClientManager();
var redisPubSub = new RedisPubSubServer(clientsManager, "channel-1", "channel-2") {
OnMessage = (channel, msg) => "Received '{0}' from '{1}'".Print(msg, channel)
}.Start();
Calling Start()
after it's initialized will get it to start listening and processing any messages published to the subscribed channels.
App Settings​
For many years our solution against using .NET's complex XML configuration for App configuration is to store structured configuration in the Web.config appSettings which thanks to the JSV format makes it easy to read and write structured data from a single string value, e.g:
<appSettings>
<add key="String" value="Foo"/>
<add key="Int" value="42"/>
<add key="List" value="A,B,C,D,E"/>
<add key="Dict" value="A:1,B:2,C:3"/>
<add key="Poco" value="{Foo:Bar}"/>
</appSettings>
This can be easily parsed into C# types with the IAppSettings API:
IAppSettings settings = new AppSettings();
string value = settings.Get("String");
int value = settings.Get("Int", defaultValue:1);
List<string> values = settings.GetList("List");
Dictionary<string,string> valuesMap = settings.GetDictionary("Dict");
MyConfig config = settings.Get("Poco", new MyConfig { Foo = "Baz" });
Like other ServiceStack providers, IAppSettings
is a clean interface with multiple providers letting you easily change or override where you want to source your App configuration from:
- DictionarySettings - Maintain settings in an in-memory Dictionary
- TextFileSettings - Maintain settings in a plain-text file
- OrmLiteAppSettings - Maintain settings in any RDBMS
Config
table
We take advantage of this in our public OSS projects when we want to override public appSettings with production settings or in our stand-alone Applications by allowing us to ship our applications with more end-user friendly plain-text config file whose defaults are embedded in the stand-alone .exe, exporting it if it doesn't exist - letting us achieve a single, portable .exe that can be xcopy'ed and run as-is.
First class AppSettings​
After proving its value over the years we've decided to make it a first-class property on IAppHost.AppSettings
which defaults to looking at .NET's App/Web.config's.
The new Chat.zip App explores different ways AppSettings can be used:
If there's an existing appsettings.txt
file where the .exe is run it will use that, otherwise it falls back to Web.config appSettings:
public AppHost() : base("Chat", typeof (ServerEventsServices).Assembly)
{
var customSettings = new FileInfo("appsettings.txt");
AppSettings = customSettings.Exists
? (IAppSettings)new TextFileSettings(customSettings.FullName)
: new AppSettings();
}
As a normal property in your AppHost, AppSettings can be accessed directly in AppHost.Configure()
:
public void Configure(Container container)
{
...
var redisHost = AppSettings.GetString("RedisHost");
if (redisHost != null)
{
container.Register<IServerEvents>(c =>
new RedisServerEvents(new PooledRedisClientManager(redisHost)));
container.Resolve<IServerEvents>().Start();
}
}
Inside your services or IOC dependencies, like any other auto-wired dependency:
public class ServerEventsServices : Service
{
public IAppSettings AppSettings { get; set; }
public void Any(PostRawToChannel request)
{
if (!IsAuthenticated && AppSettings.Get("LimitRemoteControlToAuthenticatedUsers", false))
throw new HttpError(HttpStatusCode.Forbidden, "You must be authenticated to use remote control.");
...
}
}
Directly within Razor views:
<style>
body {
background-image: url(@AppSettings.Get("background","/img/bg.jpg"))
}
</style>
As well as outside ServiceStack, via the HostContext
static class:
var redisHost = HostContext.AppSettings.GetString("redis");
AppSettings are now writable​
A new Set()
API was added to IAppSettings letting you save any serializable property that works for all providers:
public interface IAppSettings
{
void Set<T>(string key, T value);
...
}
AppSettings.Set("Poco", new MyConfig { Foo = "Baz" });
In providers that support writable configuration natively like OrmLiteAppSettings
and DictionarySettings
, the settings get written through to the underlying provider. For read-only providers like Web.config's AppSettings
or TextFileSettings
a shadowed cache is kept that works similar to prototypal shadowing in JavaScript where if a property doesn't exist, setting a property will be stored on the top-level object instance which also takes precedence on subsequent property access.
Metadata Pages​
The metadata pages have been expanded to include some of Swagger API Attribute annotations which now shows the parameters for the Request and Response DTO's as well as any other DTO's used in each metadata operation page:
When annotated the Description also shows any allowable Enum values or range limits when provided.
HtmlFormat​
The humanize feature in Auto HtmlFormat for splitting JoinedCase words with spaces can be disabled for all pages with:
HtmlFormat.Humanize = false;
Or on adhoc pages by adding #dehumanize
hash param.
Authentication​
Web Sudo​
A common UX in some websites is to add an extra layer of protection for super protected functionality by getting users to re-confirm their password verifying it's still them using the website, common in places like confirming a financial transaction.
WebSudo (by @tvjames) is a new feature similar in spirit requiring users to re-authenticate when accessing Services annotated with the [WebSudoRequired]
attribute. To make use of WebSudo, first register the plugin:
Plugins.Add(new WebSudoFeature());
You can then apply WebSudo behavior to existing services by annotating them with [WebSudoRequired]
:
[WebSudoRequired]
public class RequiresWebSudoService : Service
{
public object Any(RequiresWebSudo request)
{
return request;
}
}
Once enabled this will throw a 402 Web Sudo Required HTTP Error the first time the service is called:
var requiresWebSudo = new RequiresWebSudo { Name = "test" };
try
{
client.Send<RequiresWebSudoResponse>(requiresWebSudo); //throws
}
catch (WebServiceException)
{
client.Send(authRequest); //re-authenticate
var response = client.Send(requiresWebSudo); //success!
}
Re-authenticating afterwards will allow access to the WebSudo service.
Auth Events​
In order to enable functionality like WebSudo we've added additional hooks into the Authentication process with IAuthEvents
:
public interface IAuthEvents
{
void OnRegistered(IRequest httpReq, IAuthSession session, IServiceBase registrationService);
void OnAuthenticated(IRequest httpReq, IAuthSession session, IServiceBase authService,
IAuthTokens tokens, Dictionary<string, string> authInfo);
void OnLogout(IRequest httpReq, IAuthSession session, IServiceBase authService);
void OnCreated(IRequest httpReq, IAuthSession session);
}
These are the same authentication hooks that were previously only available when creating a Custom UserSession by inheriting AuthUserSession. The new AuthEvents API provide a loose-typed way where plugins can tap into the same hooks by registering it with AuthFeature.AuthEvents
, e.g:
public class WebSudoFeature : IPlugin, IAuthEvents
{
public void Register(IAppHost appHost)
{
...
var authFeature = appHost.GetPlugin<AuthFeature>();
authFeature.AuthEvents.Add(this);
}
// Add implementations on `IAuthEvents` handlers
public void OnCreated(IRequest httpReq, IAuthSession session)
{
...
}
...
}
An alternative way for accessing IAuthEvents
is to register it like a normal dependency, e.g:
container.RegisterAs<LogAuthEvents,IAuthEvents>();
To simplify custom implementations you can inherit from the empty concrete AuthEvents and choose to only implement the callbacks you're interested in, e.g:
public class LogAuthEvents : AuthEvents
{
public static ILog Log = LogManager.GetLogger(typeof(LogAuthEvents));
public override void OnLogout(IRequest httpReq, IAuthSession session, IServiceBase authService)
{
Log.DebugFormat("User #{0} {1} has logged out", session.UserAuthId, session.UserName);
}
}
OrmLite​
- Added new
db.ColumnLazy
API for lazily fetching a column of data - Added
db.TableExists<T>
for a typed API to detect whether a table exists - Added
INamingStrategy.GetSequenceName()
to override how sequence names in Oracle are generated - Upgraded PostgreSql Provider to Npgsql 2.2.0 and Sqlite to Sqlite.Core 1.0.93.0
Text​
- Added
JsConfig.ParsePrimitiveIntegerTypes
andJsConfig.ParsePrimitiveFloatingPointTypes
to change preferences on what primitive numeric types should be converted to. - Added
JsConfig.IgnoreAttributesNamed
to change what attributes are used to ignore properties - Added string.CountOccurancesOf() extension method
- Added Image MimeTypes
Community​
ServiceStack MiniProfiler Toolkit​
From the wider ServiceStack Community, MichaĆ Gajek has developed an alternative analyzer of ServiceStack's MiniProfiler results in a comprehensive UI that allows deep introspection of your running Services. From the Project's description:
Description​
This project intends to provide tools for collecting & analyzing profiling results of ServiceStack-based apps. Not only this makes profiling possible in the scenario when no built-in web-frontend is available (like Single Page Applications), but also has several advantages over it:
- collects & persists the results
- allows the "background" profiling (example: production environment)
- it's better to analyze large amounts of collected profiling results, not just focusing on single execution timings
- helps finding time-consuming queries
Screenshots​
Install​
PM> Install-Package Migajek.MiniProfiling.ServiceStack.RemoteStorage
Register the Plugin:​
Plugins.Add(new Migajek.Profiling.ServiceStackProfiler.MiniProfilingToolkit("http://url/", "ProjectName"));