We have another massive release in store with big updates to AutoQuery, Server Events and TypeScript support as well as greater flexibility for Multitenancy scenarios and around Service Clients.
AutoQuery Viewer​
If you've yet to try Auto Query we encourage you to check it out, it lets you effortlessly create high-performance, fully-queryable, self-descriptive services with just a single, typed Request DTO definition. As they're just normal ServiceStack Services they also benefit from ServiceStack's surrounding feature ecosystem, including native support in .NET PCL Service Clients and multi-language Add ServiceStack Reference clients. We're excited to announce even more new features for AutoQuery in this release - making it more capable and productive than ever!
AutoQuery Viewer is an exciting new feature providing an automatic UI to quickly browse and query all your AutoQuery Services!
AutoQuery Viewer is a React App that's bundled within a single ServiceStack.Admin.dll
that's available from NuGet at:
Install ServiceStack.Admin​
PM> Install-Package ServiceStack.Admin
Signed Version also available from NuGet at ServiceStack.Admin.Signed
Then to add it to your project, just register the Plugin:
Plugins.Add(new AdminFeature());
Which requires AutoQuery, if not already registered:
Plugins.Add(new AutoQueryFeature { MaxLimit = 100 });
Once enabled a link to the AutoQuery Viewer will appear under Plugin Links in your Metadata Page:
Or you can navigate to it directly at
/ss_admin/
As it's quick to add, we've already enabled it in a number of existing Live Demo's containing AutoQuery Services:
Live Examples​
- https://northwind.netcore.io/ss_admin/
- https://stackapis.netcore.io/ss_admin/
- https://techstacks.io/ss_admin/
Default Minimal UI​
By default AutoQuery Services start with a minimal UI that uses the Request DTO name to identify the Query. An example of this can be seen with the Northwind AutoQuery Services below:
[Route("/query/customers")]
public class QueryCustomers : QueryBase<Customer> {}
[Route("/query/orders")]
public class QueryOrders : QueryBase<Order> {}
Which renders a UI with the default query and initial fields unpopulated:
Marking up AutoQuery Services​
To provide a more useful experience to end users you can also markup your AutoQuery Services by annotating them
with the [AutoQueryViewer]
attribute, as seen in GitHub QueryRepos:
[Route("/repos")]
[AutoQueryViewer(IconUrl = "octicon:repo",
Title = "ServiceStack Repositories",
Description = "Browse different ServiceStack repos",
DefaultSearchField = "Language", DefaultSearchType = "=", DefaultSearchText = "C#",
DefaultFields = "Id,Name,Language,Description:500,Homepage,Has_Wiki")]
public class QueryRepos : QueryBase<GithubRepo> {}
The additional metadata is then used to customize the UI at the following locations:
Where Title
, Description
, DefaultSearchField
, DefaultSearchType
and DefaultSearchText
is a
straight forward placeholder replacement.
IconUrl​
Can either be an url to a 24x24 icon or preferably to avoid relying on any external resources,
Admin UI embeds both
Google's Material Design Icons and
GitHub's Octicon fonts which can be referenced using the custom
octicon:
and material-icons:
schemes, e.g:
- octicon:icon
- material-icons:cast
DefaultFields​
Can hold a subset list of fields from the AutoQuery Response Type in the order you want them displayed.
By default fields have a max-width of 300px but we can override this default with a :
suffix as seen
with Description:500
which changes the Description column width to 500px. Any text longer than its width
is automatically clipped, but you can still see the full-text by hovering over the field or by clicking the
AutoQuery generated link, calling the AutoQuery Service and viewing the entire results.
For more, see Advanced Customizations
Filter AutoQuery Services​
The filter textbox can be used to quickly find and browse to AutoQuery Services:
Authorized Only Queries​
Users only see Queries they have access to, this lets you further tailor the UI for users by using the
[Authenticate]
, Required Role or Permission attributes to ensure different users only see relevant queries,
e.g.
[RequiredRole("Sales")]
public class QueryOrders : QueryBase<Order> {}
Since the Auth attributes are Request Filter Attributes with a server dependency to ServiceStack.dll, in order to maintain and share a dependency-free ServiceModel.dll you should instead define a custom AutoQuery in your Service implementations which will inherit any Service or Action filter attributes as normal:
public class QueryOrders : QueryBase<Order> {}
[RequiredRole("Sales")]
public class SalesServices : Service
{
public IAutoQuery AutoQuery { get; set; }
public object Any(QueryOrders query)
{
return AutoQuery.Execute(query, AutoQuery.CreateQuery(query, Request));
}
}
Updated in Real-time​
To enable a fast and productive UX, the generated AutoQuery link and query results are refreshed as-you-type, in addition any change to a any query immediately saves the App's state to localStorage so users queries are kept across page refreshes and browser restarts.
The generated AutoQuery Url is kept in-sync and captures the state of the current query and serves as a good source for learning how to construct AutoQuery requests that can be used as-is in client applications.
Multiple Conditions​
Queries can be constructed with multiple conditions by hitting Enter or clicking on the green (+) button (activated when a condition is valid), adding it to the conditions list and clearing the search text:
Clicking the red remove icon removes the condition.
Change Content-Type​
You can force a query to return a specific Content-Type response by clicking on one of the format links. E.g clicking on json link will add the .json extension to the generated url, overriding the browser's default Content-Type to specify a JSON response:
Customize Columns​
Results can further customized to show only the columns you're interested in by clicking on the show/hide columns icon and selecting the columns you want to see in the order you want them added:
Sorting Columns and Paging Results​
Results can be sorted in descending or ascending order by clicking on the column headers:
Clicking the back/forward navigation icons on the left will page through the results in the order specified.
AutoQuery Enhancements​
We've also added a number of new features to AutoQuery that improves performance and enables greater flexibility for your AutoQuery Services:
Parameterized AutoQuery​
AutoQuery now generates parameterized sql for all queries where the {Value}
placeholder in the AutoQuery
Templates have been changed to use db parameters.
Customizable Fields​
You can now customize which fields you want returned using the new Fields
property available on all
AutoQuery Services, e.g:
?Fields=Id,Name,Description,JoinTableId
The Fields still need to be defined on the Response DTO as this feature doesn't change the Response DTO Schema, only which fields are populated. This does change the underlying RDBMS SELECT that's executed, also benefiting from reduced bandwidth between your RDBMS and App Server.
A useful JSON customization
that you can add when specifying custom fields is ExcludeDefaultValues
, e.g:
/query?Fields=Id,Name,Description,JoinTableId&jsconfig=ExcludeDefaultValues
Multiple Conditions​
Previously unsupported, AutoQuery now allows specifying multiple conditions with the same name, e.g:
?DescriptionContains=Service&DescriptionContains=Stack
Named Connection​
Related to our improved support for multi-tenancy applications, AutoQuery can easily be used to query any number of different databases registered in your AppHost.
In the example below we configure our main RDBMS to use SQL Server and register a Named Connection to point to a Reporting PostgreSQL RDBMS:
var dbFactory = new OrmLiteConnectionFactory(connString, SqlServer2012Dialect.Provider);
container.Register<IDbConnectionFactory>(dbFactory);
dbFactory.RegisterConnection("Reporting", pgConnString, PostgreSqlDialect.Provider);
Any normal AutoQuery Services like QueryOrders
will use the default SQL Server connection whilst
QuerySales
will execute its query on the PostgreSQL Reporting
Database instead:
public class QueryOrders : QueryBase<Order> {}
[NamedConnection("Reporting")]
public class QuerySales : QueryBase<Sales> {}
Generate AutoQuery Services from OrmLite T4 Templates​
Richard Safier
from the ServiceStack community has extended OrmLite's T4 Templates to include support for generating
AutoQuery Services for each Table POCO model using the new opt-in
CreateAutoQueryTypes
option whilst the new
AddNamedConnection
option can be used to generate [NamedConnection]
annotations.
With this feature Richard was able to generate thousands of fully-queryable AutoQuery Services spanning multiple databases in a single ServiceStack instance with just the T4 templates and configuration.
AdminFeature Rich UI Implementation​
We'd like to make a special mention of how AdminFeature
was built and deployed as ServiceStack makes it
really easy to package and deploy rich plugins with complex UI and behavior encapsulated within a single plugin -
which we hope spurs the creation of even richer community Plugins!
Development of AdminFeature
is maintained in a TypeScript 1.8 + JSPM + React
ServiceStack.Admin.WebHost
project where it's structured to provide an optimal iterative development experience.
To re-package the App we just call on JSPM to create our app.js bundle by pointing it to the React App's
main
entry point:
jspm bundle -m src\main ..\ServiceStack.Admin\ss_admin\app.js
Then each of the static resources are copied into the Plugins ServiceStack.Admin project with their Build Action set to Embedded Resource so they're embedded in the ServiceStack.Admin.dll.
To add the Embedded Resources to the
Virtual File System
the AdminFeature
just adds it to Config.EmbeddedResourceBaseTypes
(also making it safe to ILMerge).
The entire server implementation for the AdminFeature
is contained below, most of which is dedicated to
supporting when ServiceStack is mounted at both root /
or a custom path (e.g. /api
) - which it supports
by rewriting the embedded index.html
with the HandlerFactoryPath
before returning it:
public class AdminFeature : IPlugin, IPreInitPlugin
{
public void Configure(IAppHost appHost)
{
//Register ServiceStack.Admin.dll as an Embedded Resource to VirtualFiles
appHost.Config.EmbeddedResourceBaseTypes.Add(typeof(AdminFeature));
}
public void Register(IAppHost appHost)
{
var indexHtml = appHost.VirtualFileSources.GetFile("ss_admin/index.html").ReadAllText();
if (appHost.Config.HandlerFactoryPath != null) //Inject HandlerFactoryPath if mounted at /custom path
indexHtml = indexHtml.Replace("/ss_admin", "/{0}/ss_admin".Fmt(appHost.Config.HandlerFactoryPath));
appHost.CatchAllHandlers.Add((httpMethod, pathInfo, filePath) =>
pathInfo.StartsWith("/ss_admin")
? (pathInfo == "/ss_admin/index.html" || !appHost.VirtualFileSources.FileExists(pathInfo)
? new StaticContentHandler(indexHtml, MimeTypes.Html) as IHttpHandler
: new StaticFileHandler(appHost.VirtualFileSources.GetFile(pathInfo)))
: null);
appHost.GetPlugin<MetadataFeature>()
.AddPluginLink("/ss_admin/autoquery/", "AutoQuery Viewer"); //Add link to /metadata page
}
}
To power most of its UI, AutoQuery Viewer makes use of the existing Metadata service in AutoQuery.
Code-first POCO Simplicity​
Other classes worth reviewing is the GitHubTasks.cs and StackOverflowTasks.cs containing the NUnit tests used to create the test sqlite database on-the-fly, directly from the GitHub and StackOverflow JSON APIs, the ease of which speaks to the simplicity of ServiceStack's code-first POCO approach.
Server Events​
We've published a couple of new examples projects showing how easy it is to create rich, interactive native mobile and web apps using Server Events.
Xamarin.Android Chat​
Xamarin.Android Chat utilizes the
.NET PCL Server Events Client
to create an Android Chat App connecting to the existing
chat.netcore.io back-end where it's able to communicate with existing
Ajax clients and other connected Android Chat Apps. The example shows how to enable a native integrated
experience by translating the existing cmd.announce
message into an Android notification as well shows how to
use Xamarin.Auth to authenticate with ServiceStack using Twitter Auth.
Click the video below to see a quick demo of it in action:
For a deeper dive, checkout the feature list and source code from the AndroidXamarinChat GitHub repo.
Networked Time Traveller Shape Creator​
We've also added Server Events to convert a stand-alone Time Traveller Shape Creator into a networked one where users can connect to and watch other users using the App in real-time similar to how users can use Remote Desktop to watch another computer's screen:
Live demo at: http://redux.servicestack.net
Surprisingly most of the client code required to enable this is encapsulated within a single React Connect component.
The networked Shape Creator makes use of 2 back-end Services that lets users publish their actions to a channel and another Service to send a direct message to a User. The implementation for both services is contained below:
//Services Contract
[Route("/publish-channel/{Channel}")]
public class PublishToChannel : IReturnVoid, IRequiresRequestStream
{
public string Channel { get; set; }
public string Selector { get; set; }
public Stream RequestStream { get; set; }
}
[Route("/send-user/{To}")]
public class SendUser : IReturnVoid, IRequiresRequestStream
{
public string To { get; set; }
public string Selector { get; set; }
public Stream RequestStream { get; set; }
}
//Services Implementation
public class ReduxServices : Service
{
public IServerEvents ServerEvents { get; set; }
public void Any(PublishToChannel request)
{
var msg = request.RequestStream.ReadFully().FromUtf8Bytes();
ServerEvents.NotifyChannel(request.Channel, request.Selector, msg);
}
public void Any(SendUser request)
{
var msg = request.RequestStream.ReadFully().FromUtf8Bytes();
ServerEvents.NotifyUserId(request.To, request.Selector, msg);
}
}
Essentially just calling IServerEvents
to forward the raw JSON Request Body to the specified channel or user.
Updating Channels on Live Subscriptions​
Previously to change Server Event channel subscriptions you would need to create a new connection with the
channels you wanted to join. You can now update a live Server Events connection with Channels you want to
Join or Leave using the new built-in ServerEvents UpdateEventSubscriber
Service:
[Route("/event-subscribers/{Id}", "POST")]
public class UpdateEventSubscriber : IReturn<UpdateEventSubscriberResponse>
{
public string Id { get; set; }
public string[] SubscribeChannels { get; set; }
public string[] UnsubscribeChannels { get; set; }
}
This lets you modify your active subscription with channels you want to join or leave with a HTTP POST Request, e.g:
POST /event-subscribers/{subId}
SubscribeChannels=chan1,chan2&UnsubscribeChannels=chan3,chan4
New onUpdate Notification​
As this modifies the active subscription it also publishes a new onUpdate notification to all channel subscribers so they're able to maintain up-to-date info on each subscriber.
In C# ServerEventsClient
this can be handled together with onJoin and onLeave events using OnCommand
:
client.OnCommand = msg => ...; //= ServerEventJoin, ServerEventLeave or ServerEventUpdate
In the ss-utils JavaScript Client this can be handled with a Global Event Handler, e.g:
$(source).handleServerEvents({
handlers: {
onConnect: connectedUserInfo => { ... },
onJoin: userInfo => { ... },
onLeave: userInfo => { ... },
onUpdate: userInfo => { ... }
}
});
.NET UpdateSubscriber APIs​
Typed versions of this API is built into the C# ServerEventsClient
in both sync/async versions:
client.UpdateSubscriber(new UpdateEventSubscriber {
SubscribeChannels = new[]{ "chan1", "chan2" },
UnsubscribeChannels = new[]{ "chan3", "chan4" },
});
client.SubscribeToChannels("chan1", "chan2");
client.UnsubscribeFromChannels("chan3", "chan4");
await client.SubscribeToChannelsAsync("chan1", "chan2");
await client.UnsubscribeFromChannelsAsync("chan3", "chan4");
JavaScript UpdateSubscriber APIs​
As well as in ServiceStack's ss-utils JavaScript library:
$.ss.updateSubscriber({
SubscribeChannels: "chan1,chan2",
UnsubscribeChannels: "chan3,chan4"
});
$.ss.subscribeToChannels(["chan1","chan2"], response => ..., error => ...);
$.ss.unsubscribeFromChannels(["chan3","chan4"], response => ..., error => ...);
ServerEvents Update Channel APIs​
Whilst internally, from within ServiceStack you can update a channel's subscription using the new IServerEvents APIs:
public interface IServerEvents
{
...
void SubscribeToChannels(string subscriptionId, string[] channels);
void UnsubscribeFromChannels(string subscriptionId, string[] channels);
}
TypeScript React App (beta)​
We've spent a fair amount of time researching the JavaScript ecosystem to discover what we believe offers VS.NET developers the most optimal balance of power, simplicity and tooling to build and maintain large JavaScript Apps. ES6 offers a number of language improvements to ES5-compatible JavaScript making it much more enjoyable to develop modern applications with, which we believe justifies the additional tooling needed to transpile it to support down-level ES5 browsers. Given the lack of support for Babel/ES6 in VS.NET, the best option to access ES6 features is to use TypeScript which also offers its own benefits over and beyond ES6.
The decision to use TypeScript also meant revisiting other tools used in our Single Page App templates. One of the most productive features in ES6/TypeScript is being able to easily use modules to modularize your code which provides an optimal development experience for maintaining large and complex code-bases. For this to work seamlessly we needed to integrate TypeScript modules with our front-end JavaScript package manager which is why we've replaced bower with JSPM and configured TypeScript to use the Universal SystemJS module format.
Finally to minimize JavaScript fatigue, we've removed as much complexity and moving parts as we could and have removed Grunt in favor of leaving only a Gulp JS build system without any loss of functionality.
With these changes we've hand picked what we believe is the current Gold Standard for developing modern JavaScript Apps in VS.NET with the just released TypeScript 1.8, React, JSPM, Gulp and typings (the successor to TSD). We're also greatly benefiting from this Technology Stack ourselves with the development our latest AutoQuery Viewer TypeScript App.
We've integrated these powerful combinations of technologies and packaged it in the new TypeScript React App (Beta) VS.NET template that's now available in the updated ServiceStackVS VS.NET Extension:
To learn more about this template and explore its different features. please see the in-depth typescript-react-template guide.
TypeScript Redux​
To help developers familiarize themselves with these technologies we've also published an in-depth step-by-step guide for beginners that starts off building the simplest HelloWorld TypeScript React App from scratch then slowly growing with each example explaining how TypeScript, React and Redux can be used to easily create a more complex networked Time Travelling Shape Creator as seen in the final Example:
Live Demo: http://redux.servicestack.net
Except for the final demo above, all other examples are pure client-side only demos, i.e. without any server dependencies and can be previewed directly from the static GitHub website below:
- Example 1 - HelloWorld
- Example 2 - Modularizing HelloWorld
- Example 3 - Creating a stateful Component
- Example 4 - Change Counter to use Redux
- Example 5 - Use Provider to inject store in child Context
- Example 6 - Use connect() to make Components stateless
- Example 7 - Shape Creator
- Example 8 - Time Travelling using State Snapshots
ss-utils​
ss-utils now available on npm and DefinitelyTyped​
To make it easier to develop with ss-utils in any of the npm-based Single Page Apps templates we're maintaining a copy of ss-utils in npm and have also added it to JSPM and DefinitelyTyped registry so you can now add it to your project like any other external dependency using JSPM:
C:\> jspm install ss-utils
If you're using TypeScript, you can also download the accompanying TypeScript definition from:
C:\>typings install ss-utils --ambient --save
Or if you're using the older tsd package manager: tsd install ss-utils --save
.
New ss-utils API's​
We've added new core utils to make it easier to create paths, urls, normalize JSON responses and send POST JSON Requests:
combinePaths and createUrl​
The new combinePaths
and createUrl
API's help with constructing urls, e.g:
$.ss.combinePaths("path","to","..","join") //= path/join
$.ss.createPath("path/{foo}", {foo:1,bar:2}) //= path/1
$.ss.createUrl("http://host/path/{foo}",{foo:1,bar:2}) //= http://host/path/1?bar=2
This is a change from previous release where createUrl()
behaved like createPath()
.
normalize and normalizeKey​
The new normalizeKey
and normalize
APIs helps with normalizing JSON responses with different naming
conventions by converting each property into lowercase with any _
separators removed - normalizeKey()
converts a single string whilst normalize()
converts an entire object graph, e.g:
$.ss.normalizeKey("THE_KEY") //= thekey
JSON.stringify(
$.ss.normalize({THE_KEY:"key",Foo:"foo",bar:{A:1}})
) //= {"thekey":"key","foo":"foo","bar":{"A":1}}
const deep = true;
JSON.stringify(
$.ss.normalize({THE_KEY:"key",Foo:"foo",bar:{A:1}}, deep)
) //= {"thekey":"key","foo":"foo","bar":{"a":1}}
postJSON​
Finally postJSON
is jQuery's missing equivalent to $.getJSON
, but for POST's, eg:
$.ss.postJSON(url, {data:1}, response => ..., error => ...);
Customize JSON Responses on-the-fly​
The JSON and JSV Responses for all Services (inc. AutoQuery Services) can now be further customized with the
new ?jsconfig
QueryString param which lets your Service consumers customize the returned JSON Response to
their preference. This works similar to having wrapped your Service response in a HttpResult
with a Custom
ResultScope
in the Service implementation to enable non-default customization of a Services response, e.g:
/service?jsconfig=EmitLowercaseUnderscoreNames,ExcludeDefaultValues
Works similarly to:
return new HttpResult(new { TheKey = "value", Foo=0 }) {
ResultScope = () => JsConfig.With(
emitLowercaseUnderscoreNames:true, excludeDefaultValues:true)
};
Which results in lowercase_underscore key names with any properties with default values removed:
{"the_key":"value"}
It also supports cascading server and client ResultScopes, with the client ?jsconfig
taking precedence.
Nearly all JsConfig
scope options are supported other than delegates and complex type configuration properties.
Camel Humps Notation​
JsConfig also supports Camel Humps notation letting you target a configuration by just using the Uppercase Letters in the property name which is also case-insensitive so an equivalent shorter version of the above config can be:
?jsconfig=ELUN,edv
Camel Humps also works with Enum Values so both these two configurations are the same:
?jsconfig=DateHandler:UnixTime
?jsconfig=dh:ut
Custom JSON Live Example​
AutoQuery Viewer makes use of this feature in order to return human readable dates using the new
ISO8601DateOnly
DateHandler Enum Value as well as appending ExcludeDefaultValues
when specifying custom
fields so that any unpopulated value type properties with default values are excluded from the JSON Response.
Custom JSON Settings​
The presence of a bool configuration property will be set to true
unless they have a false
or 0
value in which case they will be set to false
, e.g:
?jsconfig=ExcludeDefaultValues:false
For a quick reference the following bool customizations are supported:
Name | Alias |
---|---|
EmitCamelCaseNames | eccn |
EmitLowercaseUnderscoreNames | elun |
IncludeNullValues | inv |
IncludeNullValuesInDictionaries | invid |
IncludeDefaultEnums | ide |
IncludePublicFields | ipf |
IncludeTypeInfo | iti |
ExcludeTypeInfo | eti |
ConvertObjectTypesIntoStringDictionary | cotisd |
TreatEnumAsInteger | teai |
TryToParsePrimitiveTypeValues | ttpptv |
TryToParseNumericType | ttpnt |
ThrowOnDeserializationError | tode |
EscapeUnicode | eu |
PreferInterfaces | pi |
SkipDateTimeConversion | sdtc |
AlwaysUseUtc | auu |
AssumeUtc | au |
AppendUtcOffset | auo |
DateHandler (dh) | |
TimestampOffset | to |
DCJSCompatible | dcjsc |
ISO8601 | iso8601 |
ISO8601DateOnly | iso8601do |
ISO8601DateTime | iso8601dt |
RFC1123 | rfc1123 |
UnixTime | ut |
UnixTimeMs | utm |
TimeSpanHandler (tsh) | |
DurationFormat | df |
StandardFormat | sf |
PropertyConvention (pc) | |
Strict | s |
Lenient | l |
You can also create a scope from a string manually using the new JsConfig.CreateScope()
, e.g:
using (JsConfig.CreateScope("EmitLowercaseUnderscoreNames,ExcludeDefaultValues,dh:ut"))
{
var json = dto.ToJson();
}
If you don't wish for consumers to be able to customize JSON responses this feature can be disabled with
Config.AllowJsConfig=false
.
Improved support for Multitenancy​
All built-in dependencies available from Service
base class, AutoQuery, Razor View pages, etc are now
resolved in a central overridable location in your AppHost.
This now lets you control which dependency is used based on the incoming Request for each Service by overriding
any of the AppHost methods below, e.g. to change the DB Connection your Service uses you can override
GetDbConnection(IRequest)
in your AppHost.
public virtual IDbConnection Db
{
get { return db ?? (db = HostContext.AppHost.GetDbConnection(Request)); }
}
public virtual ICacheClient Cache
{
get { return cache ?? (cache = HostContext.AppHost.GetCacheClient(Request)); }
}
public virtual MemoryCacheClient LocalCache //New
{
get { return localCache ?? (localCache = HostContext.AppHost.GetMemoryCacheClient(Request)); }
}
public virtual IRedisClient Redis
{
get { return redis ?? (redis = HostContext.AppHost.GetRedisClient(Request)); }
}
public virtual IMessageProducer MessageProducer
{
get { return messageProducer ?? (messageProducer = HostContext.AppHost.GetMessageProducer(Request)); }
}
Change Database Connection at Runtime​
The default implementation of GetDbConnection(IRequest)
includes an easy way to change the DB Connection
that can be done by populating the
ConnectionInfo
POCO in any
Request Filter in the Request Pipeline:
req.Items[Keywords.DbInfo] = new ConnectionInfo {
NamedConnection = ... //Use a registered NamedConnection for this Request
ConnectionString = ... //Use a different DB connection for this Request
ProviderName = ... //Use a different Dialect Provider for this Request
};
To illustrate how this works we'll go through a simple example showing how to create an AutoQuery Service that lets the user change which DB the Query is run on. We'll control which of the Services we want to allow the user to change the DB it's run on by having them implement the interface below:
public interface IChangeDb
{
string NamedConnection { get; set; }
string ConnectionString { get; set; }
string ProviderName { get; set; }
}
We'll create one such AutoQuery Service, implementing the above interface:
[Route("/rockstars")]
public class QueryRockstars : QueryBase<Rockstar>, IChangeDb
{
public string NamedConnection { get; set; }
public string ConnectionString { get; set; }
public string ProviderName { get; set; }
}
For this example we'll configure our Database to use a default SQL Server 2012 database, register an optional named connection looking at a "Reporting" PostgreSQL database and register an alternative Sqlite RDBMS Dialect that we also want the user to be able to use:
ChangeDB AppHost Registration​
container.Register<IDbConnectionFactory>(c =>
new OrmLiteConnectionFactory(defaultDbConn, SqlServer2012Dialect.Provider));
var dbFactory = container.Resolve<IDbConnectionFactory>();
//Register NamedConnection
dbFactory.RegisterConnection("Reporting", ReportingConnString, PostgreSqlDialect.Provider);
//Register DialectProvider
dbFactory.RegisterDialectProvider("Sqlite", SqliteDialect.Provider);
ChangeDB Request Filter​
To enable this feature we just need to add a Request Filter that populates the ConnectionInfo
with properties
from the Request DTO:
GlobalRequestFilters.Add((req, res, dto) => {
var changeDb = dto as IChangeDb;
if (changeDb == null) return;
req.Items[Keywords.DbInfo] = new ConnectionInfo {
NamedConnection = changeDb.NamedConnection,
ConnectionString = changeDb.ConnectionString,
ProviderName = changeDb.ProviderName,
};
});
Since our IChangeDb
interface shares the same property names as ConnectionInfo
, the above code can be
further condensed using a
Typed Request Filter
and ServiceStack's built-in AutoMapping
down to just:
RegisterTypedRequestFilter<IChangeDb>((req, res, dto) =>
req.Items[Keywords.DbInfo] = dto.ConvertTo<ConnectionInfo>());
Change Databases via QueryString​
With the above configuration the user can now change which database they want to execute the query on, e.g:
var response = client.Get(new QueryRockstars()); //SQL Server
var response = client.Get(new QueryRockstars { //Reporting PostgreSQL DB
NamedConnection = "Reporting"
});
var response = client.Get(new QueryRockstars { //Alternative SQL Server Database
ConnectionString = "Server=alt-host;Database=Rockstars;User Id=test;Password=test;"
});
var response = client.Get(new QueryRockstars { //Alternative SQLite Database
ConnectionString = "C:\backups\2016-01-01.sqlite",
ProviderName = "Sqlite"
});
ConnectionInfo Attribute​
To make it even easier to use we've also wrapped this feature in a simple
ConnectionInfoAttribute.cs
which allows you to declaratively specify which database a Service should be configured to use, e.g we can
configure the Db
connection in the Service below to use the PostgreSQL Reporting database with:
[ConnectionInfo(NamedConnection = "Reporting")]
public class ReportingServices : Service
{
public object Any(Sales request)
{
return new SalesResponse { Results = Db.Select<Sales>() };
}
}
Multi Tenancy Example​
To show how much easier it is to implement a Multi Tenancy Service with this feature we've updated the
Multi Tenancy AppHost Example
comparing it with the previous approach of implementing a Custom IDbConnectionFactory
.
New CreateQuery overloads for Custom AutoQuery​
In order for AutoQuery to pass the current IRequest
into the new AppHost.GetDbConnection(IRequest)
method
it needs to be passed when calling CreateQuery
. 2 new API's have been added that now does this:
public class MyServices : Service
{
public IAutoQuery AutoQuery { get; set; }
public object Any(Request dto)
{
var q = AutoQuery.CreateQuery(dto, base.Request);
//Calls:
//var q = AutoQuery.CreateQuery(dto, base.Request.GetRequestParams(), base.Request);
return AutoQuery.Execute(request, q);
}
}
ServiceClient URL Resolvers​
The urls used in all .NET Service Clients are now customizable with the new UrlResolver
and TypedUrlResolver
delegates.
E.g. you can use this feature to rewrite the URL used with the Request DTO Type Name used as the subdomain by:
[Route("/test")]
class Request {}
var client = JsonServiceClient("http://example.org/api") {
TypedUrlResolver = (meta, httpMethod, dto) =>
meta.BaseUri.Replace("example.org", dto.GetType().Name + ".example.org")
.CombineWith(dto.ToUrl(httpMethod, meta.Format)));
};
var res = client.Get(new Request()); //= http://Request.example.org/api/test
var res = client.Post(new Request()); //= http://Request.example.org/api/test
This feature is also implemented in JsonHttpClient
, examples below shows rewriting APIs that use custom urls:
var client = JsonHttpClient("http://example.org/api") {
UrlResolver = (meta, httpMethod, url) =>
meta.BaseUri.Replace("example.org", "111.111.111.111").CombineWith(url))
};
await client.DeleteAsync<MockResponse>("/dummy");
//=http://111.111.111.111/api/dummy
await client.PutAsync<MockResponse>("/dummy", new Request());
//=http://111.111.111.111/api/dummy
ServiceStack.Discovery.Consul​
This feature was added to make it easier to support the new ServiceStack.Discovery.Consul plugin by Scott Mackay which enables external RequestDTO endpoint discovery by integrating with Consul.io to provide automatic service registration and health checking.
To use the plugin install it from NuGet:
Install-Package ServiceStack.Discovery.Consul
Then configure your AppHost specifying the external WebHostUrl
for this Service as well as registering the
ConsulFeature
plugin:
public class AppHost : AppSelfHostBase
{
public AppHost() : base("MyService", typeof(MyService).Assembly) {}
public override void Configure(Container container)
{
SetConfig(new HostConfig {
// the url:port that other services will use to access this one
WebHostUrl = "http://api.acme.com:1234"
ApiVersion = "2.0" // optional
});
// Pass in any ServiceClient and it will be autowired with Func
Plugins.Add(new ConsulFeature(new JsonServiceClient()));
}
}
You'll also need to install and start a Consul agent after which once the AppHost is initialized you should see it automatically appear in the Consul UI and disappear after the AppHost is shutdown:
In your Services you'll then be able to use the Consul-injected IServiceClient
to call any external services
and it will automatically send the request to an active Service endpoint that handles each Request DTO, e.g:
public class MyService : Service
{
public IServiceClient Client { get; set; }
public void Any(RequestDTO dto)
{
// the client will resolve the correct uri for the external dto using consul
var response = Client.Post(new ExternalDTO { Custom = "bob" });
}
}
Health checks​
By default the plugin creates 2 health checks used to filter out failing instances of your services:
- Heartbeat: Creates an endpoint in your service http://locahost:1234/reply/json/heartbeat that expects a 200 response
- If Redis has been configured in the AppHost, it will check if Redis is responding
For more info checkout servicestack-discovery-consul GitHub Repo.
Multiple File Uploads​
New .NET APIs have been added to all .NET Service Clients that allow you to easily upload multiple streams within a single HTTP request. It supports populating Request DTO with any combination of QueryString and POST'ed FormData in addition to multiple file upload data streams:
using (var stream1 = uploadFile1.OpenRead())
using (var stream2 = uploadFile2.OpenRead())
{
var client = new JsonServiceClient(baseUrl);
var response = client.PostFilesWithRequest<MultipleFileUploadResponse>(
"/multi-fileuploads?CustomerId=123",
new MultipleFileUpload { CustomerName = "Foo,Bar" },
new[] {
new UploadFile("upload1.png", stream1),
new UploadFile("upload2.png", stream2),
});
}
Or using only a Typed Request DTO. The JsonHttpClient
also includes async equivalents for each of the new
PostFilesWithRequest
APIs:
using (var stream1 = uploadFile1.OpenRead())
using (var stream2 = uploadFile2.OpenRead())
{
var client = new JsonHttpClient(baseUrl);
var response = await client.PostFilesWithRequestAsync<MultipleFileUploadResponse>(
new MultipleFileUpload { CustomerId = 123, CustomerName = "Foo,Bar" },
new[] {
new UploadFile("upload1.png", stream1),
new UploadFile("upload2.png", stream2),
});
}
Special thanks to @rsafier for contributing support for Multiple File Uploads.
PCL WinStore Client retargeted to 8.1​
Following a VS.NET Update, we've upgraded the WinStore PCL ServiceStack.Client to target 8.1.
Local MemoryCacheClient​
As it sometimes beneficial to have access to a local in-memory Cache in addition to your registered ICacheClient
Caching Provider
we've pre-registered a MemoryCacheClient
that all your Services now have access to from the LocalCache
property, i.e:
MemoryCacheClient LocalCache { get; }
This doesn't affect any existing functionality that utilizes a cache like Sessions which continue to use
your registered ICacheClient
, but it does let you change which cache you want different responses to use, e.g:
var cacheKey = "unique_key_for_this_request";
return base.Request.ToOptimizedResultUsingCache(LocalCache, cacheKey, () => {
//Delegate is executed if item doesn't exist in cache
});
If you don't register a ICacheClient
ServiceStack automatically registers a MemoryCacheClient
for you
which will also refer to the same instance registered for LocalCache
.
Cookies​
If you're using a Custom AuthProvider
that doesn't rely on Session Cookies you can disable them from being
created with Config.AllowSessionCookies=false
. The Cookie behavior can be further customized by overriding
AllowSetCookie()
in your AppHost, E.g. you can disable all cookies with:
public override bool AllowSetCookie(IRequest req, string cookieName)
{
return false;
}
Redis​
OrmLite​
New [EnumAsInt]
attribute as an alternative to [Flags]
for storing Enums as ints in OrmLite but still
have them serialized as strings in Service responses.
Free-text SQL Expressions are now converted to Parameterized Statements, e.g:
var q = db.From<Rockstar>()
.Where("Id < {0} AND Age = {1}", 3, 27);
var results = db.Select(q);
Select Fields​
The new Select API on SqlExpression enables a resilient way to select custom fields matching the first column it
finds (from primary table then joined tables). It also fully qualifies field names to avoid ambiguous columns,
allows matching of joined tables with {Table}{Column}
convention and ignores any non-matching fields, e.g:
var q = db.From<Rockstar>()
.Join<RockstarAlbum>((r,a) => r.Id == a.RockstarId)
.Select(new[] { "Id", "FirstName", "Age", "RockstarAlbumName", "_unknown_" });
Other OrmLite Changes​
- All
db.Exists()
APIs have been optimized to only query a single column and row. - New
db.SelectLazy()
API added that accepts an SqlExpression - Max String column definition for MySQL now uses LONGTEXT
ServiceStack.Text​
- New
JsConfig.SkipDateTimeConversion
to skip built-in Conversion of DateTime's. - New
ISO8601DateOnly
andISO8601DateTime
DateHandler formats to emit only the Date or Date and Time
Stripe Gateway​
Added support for serializing nested complex entities using Stripe's unconventional object notation and
the new CreateStripeAccount
requiring it, e.g:
var response = gateway.Post(new CreateStripeAccount
{
Country = "US",
Email = "test@email.com",
Managed = true,
LegalEntity = new StripeLegalEntity
{
Address = new StripeAddress
{
Line1 = "1 Highway Rd",
City = "Brooklyn",
State = "NY",
Country = "US",
PostalCode = "90210",
},
Dob = new StripeDate(1980, 1, 1),
BusinessName = "Business Name",
FirstName = "First",
LastName = "Last",
}
});
Which sends a POST Form Data request that serializes the nested Dob into the object notation Stripe expects, e.g:
&legal_entity[dob][year]=1970&legal_entity[dob][month]=1&legal_entity[dob][day]=1
Minor ServiceStack Features​
- Old Session removed and invalided when generating new session ids for a new AuthRequest
- New ResourcesResponseFilter, ApiDeclarationFilter and OperationFilter added to SwaggerFeature to modify response
Name
property added toIHttpFiles
in Response.Files collectionHostType
,RootDirectoryPath
,RequestAttributes
,Ipv4Addresses
andIpv6Addresses
added to ?debug=requestinfoStaticFileHandler
now hasIVirtualFile
andIVirtualDirectory
constructor overloads- New
StaticContentHandler
for returning custom text or binary responses inRawHttpHandlers
Changes​
IRequest.GetRequestParams()
Dictionary now returns any duplicate fields with a hash + number suffix, e.g: #1
.
To retain existing behavior where duplicate values are merged into a ,
delimited string use
IRequest.GetFlattenedRequestParams()
HttpListener now returns the RemoteEndPoint
as the UserHostAddress
matching the UserHostAddress
returned
in ASP.NET Web Applications.
WARNING .NET 4.0 builds will cease after August 1, 2016​
Microsoft has discontinued supporting .NET 4.0, 4.5 and 4.5.1 as of January 12th, 2016. We've already started seeing a number of 3rd Party NuGet packages already drop support for .NET 4.0 builds which has kept us referencing old versions. As a result we intend to follow and stop providing .NET 4.0 builds ourselves after August 1st, 2016. If you absolutely need access to .NET 4.0 builds after this date please leave a comment on this UserVoice entry.