Edit on GitHub

v4.0.18 Release Notes

New, much faster Self-Host!

Prior to this release ServiceStack had 2 self-hosting options with different Concurrency Models:

Where in typical scenarios (i.e. CPU intensive or blocking IO), executing on .NET’s Thread Pool provides better performance.

This Self-hosting performance analysis from the ServiceStack community shows we’re able to achieve even better performance by utilizing the excellent Smart Thread Pool instead, which is now available in the AppHostHttpListenerSmartPoolBase base class.

The new Smart Pool self-host routinely outperforms all other self hosting options, and does especially well in heavy IO scenarios as seen in the benchmarks below:

Self Host ASP.NET/IIS Express HttpListener Pool HttpListener
Database updates 1x 1.9x 2x 4.1x
Single database query 1x 1.2x 1.5x 2.6x
Multiple database queries 1x 1.2x 1.4x 2.6x
Plaintext 1x 2.3x 2.4x 1.6x
Fortunes Razor View 1x 1.2x 1.5x 1.8x
JSON serialization 1x 1.2x 1.4x 1x

Using different Self Host options

You can easily switch between the different self-hosting options by simply changing your AppHost’s base class, e.g:

public class AppHost : AppHostHttpListenerBase { ... }
public class AppHost : AppHostHttpListenerPoolBase { ... }
public class AppHost : AppHostHttpListenerSmartPoolBase { ... }

Both the HttpListener Pool and SmartPool hosts have configurable pool sizes that can be tweaked to perform better under different scenarios.

Optimal Self Hosted option

As the number of self-hosts grow, we’ve added a new AppSelfHostBase base class that represents an alias for the highest performing self-hosting option with an optimal configuration that we’ll continue to tune for performance against typical scenarios. Unless you’ve identified specific configurations that performs better for your use-case, the recommendation is for new self-hosts to inherit this configuration:

public class AppHost : AppSelfHostBase { ... }

OrmLite

OrmLite received a lot more attention this release with a number of value-added additions:

Improved Oracle RDBMS provider

The OrmLite Oracle Provider has been significantly improved thanks to Bruce Cowen efforts who’s brought the quality in-line with other RDBMS providers which now passes OrmLite’s test suite. As part of this change, the Oracle Provider now depends on Oracle’s Data Provider for .NET and can be installed with:

PM> Install-Package ServiceStack.OrmLite.Oracle
PM> Install-Package ServiceStack.OrmLite.Oracle.Signed

More notes about the Oracle provider are maintained in the OrmLite Release Notes.

Improved Typed SqlExpressions

The existing db.SqlExpression<T>() API has a more readable alias in:

db.From<Table>();

Which now supports an optional SQL FROM fragment that can be used to specify table joins, e.g:

var results = db.Select(db.From<Person>("Person INNER JOIN Band ON Person.Id = Band.PersonId"));

New ISqlExpression API

OrmLite API’s have overloads to execute any SQL builders that implement the simple ISqlExpression API, i.e:

public interface ISqlExpression
{
    string ToSelectStatement();
}

This allows for more readable code when using a decoupled Sql Builder, e.g:

int over40s = db.Scalar<int>(db.From<Person>().Select(Sql.Count("*")).Where(q => q.Age > 40));

List<string> lastNames = db.Column<string>(db.From<Person>().Select(x => x.LastName).Where(q => q.Age == 27));

HashSet<int> uniqueAges = db.ColumnDistinct<int>(db.From<Person>().Select(x => x.Age).Where(q => q.Age < 50));

Dictionary<int,string> map = db.Dictionary<int,string>(db.From<Person>().Select(x => new {x.Id, x.LastName}));

Partial Selects

This also improves the APIs for partial SELECT queries, which originally required the use of custom SQL:

var partialColumns = db.SelectFmt<SubsetOfShipper>(typeof(Shipper), "ShipperTypeId = {0}", 2);

But can now be expressed in any of the more typed examples below:

var partialColumns = db.Select<SubsetOfShipper>(db.From<Shipper>().Where(q => q.ShipperTypeId == 2));

Or partially populating the same POCO with only the columns specified:

var partialColumns = db.Select<Shipper>(q => q.Select(x => new { x.Phone, x.CompanyName })
                                              .Where(x => x.ShipperTypeId == 2));

var partialColumns = db.Select<Shipper>(q => q.Select("Phone, CompanyName")
                                              .Where(x => x.ShipperTypeId == 2));

Nullable Limit APIs

The Limit API’s now accept int? making it easier to apply paging in your ServiceStack services, e.g:

public Request 
{
    public int? Skip { get; set; }
    public int? Take { get; set; }
}

public List<Table> Any(Request request)
{
    return Db.Select(db.From<Table>.Limit(request.Skip, request.Take));
}

Which will only filter the results for the values provided. Aliases for Skip() and Take() are also available if LINQ naming is preferred.

New AliasNamingStrategy

A new alias naming strategy was added (in addition to [Alias] attribute) that lets you specify a dictionary of Table and Column aliases OrmLite should used instead, e.g:

OrmLiteConfig.DialectProvider.NamingStrategy = new AliasNamingStrategy {
    TableAliases  = { { "MyTable", "TableAlias" } },
    ColumnAliases = { { "MyField", "ColumnAlias" } },
};

Which OrmLite will use instead, e.g when creating a table:

db.CreateTable<MyTable>();

Aliases can also be referenced when creating custom SQL using the SqlTable() and SqlColumn() extension methods, e.g:

var result = db.SqlList<MyTable>(
    "SELECT * FROM {0} WHERE {1} = {2}".Fmt(
        "MyTable".SqlTable(),
        "MyField".SqlColumn(), "foo".SqlValue()));

New Exists APIs

Nicer if you just need to check for existence, instead of retrieving a full result-set e.g:

bool hasUnder50s = db.Exists<Person>(x => x.Age < 50);
bool hasUnder50s = db.Exists(db.From<Person>().Where(x => x.Age < 50));

Redis

New Scan APIs Added

Redis v2.8 introduced a beautiful new SCAN operation that provides an optimal strategy for traversing a redis instance entire keyset in managable-size chunks utilizing only a client-side cursor and without introducing any server state. It’s a higher performance alternative and should be used instead of KEYS in application code. SCAN and its related operations for traversing members of Sets, Sorted Sets and Hashes are now available in the Redis Client in the following API’s:

public interface IRedisClient
{
    ...
    IEnumerable<string> ScanAllKeys(string pattern = null, int pageSize = 1000);
    IEnumerable<string> ScanAllSetItems(string setId, string pattern = null, int pageSize = 1000);
    IEnumerable<KeyValuePair<string, double>> ScanAllSortedSetItems(string setId, string pattern = null, int pageSize = 1000);
    IEnumerable<KeyValuePair<string, string>> ScanAllHashEntries(string hashId, string pattern = null, int pageSize = 1000);    
}

//Low-level API
public interface IRedisNativeClient
{
    ...
    ScanResult Scan(ulong cursor, int count = 10, string match = null);
    ScanResult SScan(string setId, ulong cursor, int count = 10, string match = null);
    ScanResult ZScan(string setId, ulong cursor, int count = 10, string match = null);
    ScanResult HScan(string hashId, ulong cursor, int count = 10, string match = null);
}

The IRedisClient provides a higher-level API that abstracts away the client cursor to expose a lazy Enumerable sequence to provide an optimal way to stream scanned results that integrates nicely with LINQ, e.g:

var scanUsers = Redis.ScanAllKeys("urn:User:*");
var sampleUsers = scanUsers.Take(10000).ToList(); //Stop after retrieving 10000 user keys 

New HyperLog API

The development branch of Redis server (available when v3.0 is released) includes an ingenious algorithm to approximate the unique elements in a set with maximum space and time efficiency. For details about how it works see Redis’s creator Salvatore’s blog who explains it in great detail. Essentially it lets you maintain an efficient way to count and merge unique elements in a set without having to store its elements. A Simple example of it in action:

redis.AddToHyperLog("set1", "a", "b", "c");
redis.AddToHyperLog("set1", "c", "d");
var count = redis.CountHyperLog("set1"); //4

redis.AddToHyperLog("set2", "c", "d", "e", "f");

redis.MergeHyperLogs("mergedset", "set1", "set2");

var mergeCount = redis.CountHyperLog("mergedset"); //6

HTTP and MQ Service Clients

Substitutable OneWay MQ and HTTP Service Clients

Service Clients and MQ Clients have become a lot more interoperable where all MQ Clients now implement the Service Clients IOneWayClient API which enables writing code that works with both HTTP and MQ Clients:

IOneWayClient client = GetClient();
client.SendOneWay(new RequestDto { ... });

Likewise the HTTP Service Clients implement the Messaging API IMessageProducer:

void Publish<T>(T requestDto);
void Publish<T>(IMessage<T> message);

When publishing a IMessage<T> the message metadata are sent as HTTP Headers with an X- prefix.

UploadProgress added on Service Clients

Which works similar to OnDownloadProgress where you can specify a callback to provide UX Progress updates, e.g:

client.OnUploadProgress = (bytesWritten, total) => "Written {0}/{1} bytes...".Print(bytesWritten, total);

client.PostFileWithRequest<UploadResponse>(url, new FileInfo(path), new Upload { CreatedBy = "Me" });

Razor Support

Our support for No Ceremony Razor pages has been very well received which has all but alleviated the need of requiring services / controllers for dynamic html pages. One of the areas where a Service may be required is for execution any custom request filters, which we’ve now added support for by letting you choose to execute all request filters for a specific Request with:

@{
    ApplyRequestFilters(new RequestDto());
}

This will execute all the Request Filters applied to the specified Request DTO. Any one of the filters ends the request (e.g. with a redirect) and the rest of the Razor page will stop execution.

Likewise it’s possible to redirect from within Razor with:

@{
    if (!IsAuthenticated) {
        Response.RedirectToUrl("/login");
        throw new StopExecutionException();
    }
}

An alternative to StopExecutionException is to have an explicit return;, the difference being that it will continue to execute the remainder of the page, although neither approach will emit any Razor output to the response.

As redirecting non-authenticated users is a common use-case it’s also available as a one-liner:

@{
    RedirectIfNotAuthenticated();
}

Which if no url is specified it will redirect to the path configured on AuthFeature.HtmlRedirect.

ss-utils.js

A few enhancements were added to ServiceStack’s /js/ss-utils.js is ServiceStack’s built-in JS library, first demonstrated in Email Contacts solution:

Declarative event handlers can send multiple arguments:

<ul>
    <li data-click="single">Foo</li>
    <li data-click="multiple:arg1,arg2">Bar</li>
</ul>
$(document).bindHandlers({
    single: function(){
        var li = this;
    },
    multiple: function(arg1, arg2) {
        var li = this;
    }
});

Trigger client-side validation errors with setFieldError():

$("form").bindForm({
    validate: function(){
        var params = $(this).serializeMap();
        if (params.Password != params.Confirm){
            $(this).setFieldError('Password', 'Passwords to not match');
            return false;
        }
    }
});

Model binding now also populates data-href and data-src attributes e.g:

<a data-href="FieldName"><img data-src="FieldName" /></a>
$("form").applyValues({ FieldName: imgUrl });

Other Changes

Restriction attributes allowed on Services

Restriction attributes can be added on Service classes in addition to Request DTOs (which still take precedence).

[Restrict(LocalhostOnly = true)]
public class LocalHostOnlyServices : Service { ... }

AppSettings

New OrmLiteAppSettings

Added new read/write AppSettings config option utilizing OrmLite as the back-end. This now lets you maintain your applications configuration in any RDBMS back-end OrmLite supports. It basically works like a mini Key/Value database in which can store any serializable value against any key which is maintained into the simple Id/Value ConfigSettings table.

Usage

Registration just uses an OrmLite DB Factory, e.g:

container.Register(c => new OrmLiteAppSettings(c.Resolve<IDbConnectionFactory>()));
var appSettings = container.Resolve<OrmLiteAppSettings>();
appSettings.InitSchema(); //Create the ConfigSettings table if it doesn't exist

It then can be accessed like any AppSetting APIs:

//Read the `MyConfig` POCO stored at `config` otherwise use default value if it doesn't exist
MyConfig config = appSettings.Get("config", new MyConfig { Key = "DefaultValue" });

It also supports writing config values in addition to the AppSettings read-only API’s, e.g:

var latestStats = appSettings.GetOrCreate("stats", () => statsProvider.GetLatest());

Extract key / value settings from text file

The new ParseKeyValueText extension method lets you extract key / value data from text, e.g:

var configText = @"
StringKey string value
IntKey 42
ListKey A,B,C,D,E
DictionaryKey A:1,B:2,C:3,D:4,E:5
PocoKey {Foo:Bar,Key:Value}";

Dictionary<string, string> configMap = configText.ParseKeyValueText(delimiter:" ");

When combined with the existing DictionarySettings, enables a rich, simple and clean alternative to .NET’s App.config config section for reading structured configuration into clean data structures, e.g:

IAppSettings appSettings = new DictionarySettings(configMap);

string value = appSettings.Get("StringKey");

int value = appSettings.Get("IntKey", defaultValue:1);

List<string> values = appSettings.GetList("ListKey");

Dictionary<string,string> valuesMap = appSettings.GetList("DictionaryKey");

MyConfig config = appSettings.Get("PocoKey", new MyConfig { Key = "DefaultValue"});

As we expect this to be a popular combination we’ve combined them into a single class that accepts a filePath, providing a simple alternative to custom Web.config configurations:

var appSettings = new TextFileSettings("~/app.settings".MapHostAbsolutePath());

PerfUtils

We’ve included the C# Benchmark Utils previously used in Sudoku Benchmarks originally inspired from Dart’s benchmark_harness. Unlike other benchmark utils, it runs for a specified period of time (2000ms by default) then returns the avg iteration time in microseconds. Here’s an example usage comparing performance of maintaining a unique int collection between HashSet vs List:

var rand = new Random();
var set = new HashSet<int>();
var avgMicroSecs = PerfUtils.Measure(
    () => set.Add(rand.Next(0, 1000)), runForMs:2000);

"HashSet: {0}us".Print(avgMicroSecs);

var list = new List<int>();
avgMicroSecs = PerfUtils.Measure(() => {
        int i = rand.Next(0, 1000);
        if (!list.Contains(i))
            list.Add(i);
    }, runForMs: 2000);

"List: {0}us".Print(avgMicroSecs);

Minor Changes

New Signed Projects

Breaking Changes