We've got something super special in store in this release which has completely changed how we develop .NET server-generated Web Apps that includes a new approach to dramatically simplify .NET Web App development, providing a highly productive development experience whilst maximizing reuse and component sharing. This release also brings support for .NET Core 2.0, new providers for Azure which includes an MQ Server for Azure ServiceBus, a Virtual File System for Azure Blob Storage and a Caching Provider for Azure Table Storage.
With our abstractions containing simple clean interfaces supporting multiple implementations and OrmLite and AutoQuery supporting multiple RDBMS's behind the same Typed API, it's become easier than ever to run your ServiceStack Apps on either a managed AWS or Azure infrastructure.
.NET Core 2.0 Ready​
Firstly we'd like to announce this Release adds support for the newly released .NET Core 2.0. Our test suites have been upgraded to run .NET Core 2.0 as well as some of our existing .NET Core Apps. All new Web Apps created in this release were developed on .NET Core 2.0 which we believe is the first .NET Core release that should be given first consideration for development of new greenfield .NET Web Apps to see if it's able to meet your requirements. Its extensibility and simplified dev model and ability to run flawlessly cross-platform gives your investments a lot more utility, including being able to take advantage of the lower cost, simplified dev model and superior automation from being able to deploy to Linux whilst still allowing each developer to use their preferred Desktop OS and development environment.
Preparing for V5​
Now that both .NET Standard 2.0 .NET Core 2.0 have been released our next release will be a major V5 release where we'll be performing the following structural changes:
- Upgrading to NuGet v3 specs which allows specifying different dependencies for different builds
- Merging the .NET Core packages into main packages where they'll share the same version and release cadence
- Upgrade libraries to .NET Standard 2.0
- Drop PCL and Silverlight builds in favor of .NET Standard
- Remove deprecated APIs (Except for OrmLite's legacy deprecated APIs)
- Move Razor Helpers & Markdown Razor to ServiceStack.Razor
- Upgrade RabbitMQ.Client to v5.x, requiring .NET v4.5.1+
- Sign all packages and release our signing key
We'll keep the voting open on whether or not we should we should Sign all packages for another 2 weeks. Please vote either way if you'd prefer to have ServiceStack binaries signed or not.
In terms of preparation for v5, we'll be keeping source compatibility a top priority and we don't envisage there to be too many source breaking changes except for removing the deprecated APIs so the primary task before upgrading to V5 will be to move off deprecated APIs. Most Obsolete APIs specify which APIs to move to in their deprecated messages which you can find in your build warning messages.
#Script
!​
We're super excited to announce #Script. At its core #Script
is a simple, fast and versatile general-purpose dynamic templating language for .NET and .NET Core. It requires no pre-compilation, is lazily loaded and Starts up instantly with fast runtime performance, is late-bound with no binary coupling, is highly extensible and is evaluated in a Sandbox with complete fine-grain control over what functionality is available to templates running in different contexts.
These characteristics opens up #Script
into a number of exciting new use-cases, some of which we cover in our comprehensive and interactive documentation at sharpscript.net.
Starter Projects​
The Starter Projects below provide a quick way to get started with a pre-configured ServiceStack Template App:
.NET Core 2.0 Bootstrap Starter​
Clone the TemplatesBootstrapStarter GitHub project to start from a Bootstrap v4 and jQuery .NET Core 2.0 App:
ASP.NET v4.5 Bootstrap Starter​
For ASP.NET v4.5 projects create a new ServiceStack ASP.NET Templates with Bootstrap from the VS.NET Templates in ServiceStackVS VS.NET Extension to create an ASP.NET v4.5 Project using ServiceStack's recommended project structure:
Why Templates?​
Whilst we expect to see Templates used in a number of new use-cases we haven't thought of yet, it's primarily used as an alternative to Razor for developing server-generated Web Apps. Given Razor is so pervasive in .NET it may come as a surprise as to why we'd want an alternative.
The Razor Problem​
In summary, it's because we want to be able to offer a simple, clean, highly-productive and innovative end-to-end solution for building ServiceStack Web Apps without the external baggage and issues Razor brings to a project. If interested in the finer details, we've published some of the limitations and issues we've hit in Why not Razor.
Of particular concern is the invasive magic behavior it relies on which trades its hidden complexity with making Projects not using it appear more complex by virtue of being forced to include special configuration to opt-out of Razor's hidden magic behavior breaking their Apps. We're disappointed to see this practice continue in .NET Core which is actively harmful to all .NET Core Web Projects not using MVC or Razor.
Razor's fragility, complexity and limitations imposed by its surrounding tooling prevents us from continuing to innovate around Razor further and has caused us to remove it from our Single Page App Templates which adds marginal value in SPA's that doesn't justify the added configuration, complexity, infrastructure and potential source of issues it adds to projects.
Utilizing ASP.NET's stewardship of Razor in .NET Core​
In a testament to ASP.NET Core's development model, one of the benefits it enables is being able to seamlessly integrate different frameworks together within the same HTTP Request pipeline. The rewrite of ServiceStack's Razor features on top of MVC's Razor means we both share the same Razor implementation which ServiceStack projects benefit from by automatically having access to new features the ASP.NET team adds to Razor.
End User Language with low ROI​
Another reason for our reduced focus around Razor is similar to VB6's one-way consumption of COM APIs, Razor is an "end-user language" for .NET APIs, i.e. server logic embedded in Razor pages is "one-off code" providing minimal utility and code reuse that's only applicable to HTML clients (i.e. Browsers) for the page it's embedded in whilst delivering a worse UX compared to the alternative API First Development model where Web Pages use Ajax as just another client to call the same back-end Services that Mobile and Desktop clients use.
So instead of having browsers perform full-page POST backs and creating specific Controllers/Services that can only handle browser requests, you'll get better responsiveness, utility and code-reuse by just developing "pure" back-end Services and using JavaScript to make Ajax requests. JavaScript is also much better than C# at being able to use a generic routine to automatically update Form UIs with Service's structured error responses where it benefits from reduced development effort.
In this light it's more important to interoperate with JavaScript than it is to have C# logic embedded in HTML pages.
Endless pursuit of Value and Simplicity​
For the various reasons above our focus for Web Apps has shifted towards providing the simplest and most productive development experience using the best-in-class npm-based tooling for the most popular JavaScript frameworks. As JavaScript frameworks and tooling continue to evolve it's become a delicate balance at adopting a modern JavaScript stack with tools that integrate well together and yield the most value for minimal amount of tooling, configuration and complexity. We believe our recommended Webpack and TypeScript Integrated SPA VS.NET Templates scores well within these constraints that provides a productive development experience whilst generating optimal production build outputs.
At the same time adopting a modern SPA development stack has become an overkill solution for many small and medium-sized projects and there exists a large class of websites that still benefit from being a traditional server-generated Website.
But if not a modern SPA stack, what other options are there for .NET? For .NET Core you can use JavaScript Services to integrate .NET and JavaScript, but this adds even more complexity and overhead that's destined to always be a 2nd Rate experience to using a pure node.js development stack directly. You could use ServiceStack with a static generator like nuxt.js, this provides a clean separation for your front-end UI and backend Services but that requires managing 2 different development frameworks requiring additional build steps and can only integrate via Ajax. Falling back to Razor is still a viable option but we dislike its baggage and issues and would prefer to spend our complexity budget on a SPA Stack which yields a more productive development workflow and better end user UX.
A new .NET templating language was born​
The remaining option is to build the server templating language we wanted, one without the complexity, issues, design problems and static coupling of Razor, with great Startup and runtime performance, is highly-extensible and promotes reuse, integrates cleanly with .NET but still adopts the strengths that make the premier JavaScript frameworks enjoyable and productive to build HTML UIs with. The decision to use JavaScript syntax instead of C# was based on our experience using both where:
- Most innovation in HTML UIs is happening in JavaScript
- Server generated HTML needs to interop with JavaScript
- JavaScript is more productive at HTML UIs than C#
- JavaScript syntax is smaller and simpler than C#
- JavaScript lets us use a common language for client/server logic in HTML
We didn't want to invent a new syntax so we evaluated various syntax from multiple JavaScript frameworks and ultimately settled on Vue.js Filters syntax which had several benefits going for it:
- It's simple and intuitive
- It's not coupled to any text format
- It works well with HTML and HTML designers/editors
- It has minimal, wrist-friendly syntax
- It's composable and expressive
- It's declarative and functional
Above all we share Vue's primary focus on simplicity and its incremental approach to layering advanced functionality, a goal that drove the design and development of #Script
. The Syntax is essentially compatible with Vue.js filters including supporting JavaScript's syntax for its native data types and function calls. The only extensions we've added is a wrist-friendly syntax for single string arguments and an additional syntax for defining string literals using Prime Quotes.
Within this minimal syntax we've been able to achieve a highly versatile dynamic template language whose expressive power comes from its filters of which we've included a comprehensive suite to handle many of the tasks commonly required in Templates and Web Apps.
Meet #Script
​
Even in this initial release we're extremely pleased with its current form, we've spent less time developing Templates than we have on ServiceStack's Razor Integration and we've already innovated past what we've been capable to do with Razor. It's not coupled to any external tooling or susceptible to any of the external factors that has plagued us with Razor. It's highly testable by design with unit tests being trivial to write that it's our most tested feature with over 350 new tests added to support its current feature-set, it's also our most documented feature.
It's small, lightweight footprint and built-in Hot Reloading provides a fun, clean and productive alternative to MVC Razor that's easily integrated into any web framework and runs identically in every platform ServiceStack runs on, it can also be returned in ASP.NET MVC and ASP.NET MVC Core Controllers - in all cases, using the same high-performance implementation to asynchronously write to a forward-only OutputStream for max performance and maximum potential reuse of your code.
Templates are lazily loaded and late-bound for Instant Startup, doesn't require any pre-compilation, have coupling to any external configuration files, build tools, designer tooling or have any special deployment requirements. It can be used as a general purpose templating language to enhance any text format and includes built-in support for .html
.
Templates are evaluated in an Isolated Sandboxed that enables fine-grained control over exactly what functionality and instances are available to different Templates. They're pre-configured with a comprehensive suite of safe Default Filters which when running in trusted contexts can easily be granted access to enhanced functionality.
Templates are designed to be incrementally adoptable where its initial form is
ideal for non-programmers,
that can gradually adopt more power and functionality when needed where they can leverage existing Services or MVC Controllers to enable an MVC programming model or have .html
pages upgraded to use
Code Pages where they can utilize the full unlimited power of the C# programming language to enable precise control over the rendering of pages and partials. Code pages take precedence and are interchangeable wherever normal .html
pages are requested making them a non-invasive layered solution whenever advanced functionality is required.
Surrounding Ecosystem​
These qualities opens Templates up to a number of new use-cases that's better suited than Razor for maintaining content-heavy websites, live documents, Email Templates and can easily introspect the state of running .NET Apps where they provide valuable insight at a glance with support for Adhoc querying.
Web Apps​
One use-case made possible by Templates we're extremely excited about is Web Apps - a new approach to dramatically simplify .NET Web App development and provide the most productive development experience possible whilst maximizing reuse and component sharing.
Web Apps leverages Templates to develop entire content-rich, data-driven websites without needing to write any C#, compile projects or manually refresh pages - resulting in the easiest and fastest way to develop Web Apps in .NET!
Ultimate Simplicity​
Not having to write any C# code or perform any app builds dramatically reduces the cognitive overhead and conceptual knowledge required for development where the only thing front-end Web developers need to know is Template's syntax and what filters are available to call. Because of Template's high-fidelity with JavaScript, developing a Website with Templates will be instantly familiar to JavaScript developers despite calling and binding directly to .NET APIs behind the scenes.
All complexity with C#, .NET, namespaces, references, .dlls, strong naming, packages, MVC, Razor, build tools, IDE environments, etc has been eliminated leaving all Web Developers needing to do is run a cross-platform web/app.dll .NET Core 2.0 executable and configure a simple app.settings text file to specify which website folder to use, which ServiceStack features to enable, which db or redis providers to connect to, etc. Not needing to build also greatly simplifies deployments where multiple websites can be deployed with a single rsync or xcopy command or if deploying your App in a Docker Container, you just need to copy your website files, or just the app.settings
if you're using an S3 or Azure Virtual File System.
Rapid Development Workflow​
The iterative development experience is also unparalleled for a .NET App, no compilation is required so you can just leave the web/app.dll
running whilst you add the template .html
files needed to build your App and thanks to the built-in Hot Reloading support, pages will refresh automatically as you save. You'll just need to do a full page refresh when modifying external .css/.js files to bypass the browser's cache and you'll need to restart web/app.dll
to pick up any changes to your app.settings
or .dlls to your /plugins
folder.
Pure Cloud Apps​
Web Apps also enable the development of Pure Cloud Apps where the same Web App can be developed and run entirely on AWS S3 and RDS or Azure Blob Storage and SQL Server by just changing the app.settings
that's deployed with the pre-compiled Web App Binary.
Example Web Apps​
We've developed a number of Web Apps to illustrate the various features available and to showcase its strengths and the different kind of Web Apps that can easily be developed with it. The source code for each app is maintained in NetCoreWebApps and each Web App runs the same pre-compiled web/app.dll binary.
Bare Web App​
source /Bare Web App - demo bare.web-app.io
The Web App Starter project is representative of a typical Company splash Website:
The benefits over using a static website is improved maintenance as you can extract and use its common _layout.html instead of having it duplicated in each page. The menu.html partial also makes menu items easier to maintain by just adding an entry in the JavaScript object literal. The dynamic menu also takes care of highlighting the active menu item.
{‎{ { '/': 'Home',
'/about': 'About',
'/services': 'Services',
'/contact': 'Contact'
} | toList | assignTo: links }‎}
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
{‎{ links | select: <li class="nav-item { 'active' | ifMatchesPathInfo(it.Key) }"><a class="nav-link" href="{ it.Key }">{ it.Value }</a></li> }‎}
</ul>
</div>
Ideal for Web Designers and Content Authors​
The other primary benefit is that this is an example of a website that can be maintained by employees who don't have any programming experience as Templates in their basic form are intuitive and approachable to non-developers, e.g: The title of each page is maintained as metadata HTML comments:
<!--
title: About Us
-->
Template's syntax is also the ideal way to convey variable substitution, e.g: <title>{‎{ title }‎}</title>
and even embedding a partial reads like english {‎{ 'menu' | partial }‎}
which is both intuitive and works well with GUI HTML designers.
app.settings​
Below is the app.settings
for a Basic App:
debug true
name Bare WebApp
debug true
controls the level of internal diagnostics available and whether or not Hot Reloading is enabled.
Redis Web App​
source /RedisHtml - demo redis-html.web-app.io
For the Redis Browser Web App, we wanted to implement an App that was an ideal candidate for a Single Page App but constrain ourselves to do all HTML rendering on the server and have each interaction request a full-page reload to see how a traditional server-generated Web App feels like with the performance of .NET Core 2.0 and Templates. We're pleasantly surprised with the result as when the App is run locally the responsiveness is effectively indistinguishable from an Ajax App. When hosted on the Internet there is a sub-second delay which causes a noticeable flicker but it still retains a pleasant UX that's faster than most websites.
The benefits of a traditional website is that it doesn't break the web where the back button and deep linking work without effort and you get to avoid the complexity train of adopting a premier JavaScript SPA Framework's configuration, dependencies, workflow and build system which has become overkill for small projects.
We've had a sordid history developing Redis UI's which we're built using the popular JavaScript frameworks that appeared dominant at the time but have since seen their ecosystem decline, starting with the Redis Admin UI built using Google's Closure Library that as it works different to everything else needed a complete rewrite when creating redisreact.servicestack.net using the hot new React framework, unfortunately it uses React's old deprecated ES5 syntax and Reflux which is sufficiently different from our current recommended TypeScript + React + Redux + WebPack JavaScript SPA Stack, that is going to require a significant refactor to adopt our preferred SPA tech stack.
Beautiful, succinct, declarative code​
The nice thing about generating HTML is that it's the one true constant in Web development that will always be there. The entire functionality for the Redis Web App is contained in a single /RedisHtml/index.html which includes all Template and JavaScript Source Code in < 200 lines which also includes all as server logic as it doesn't rely on any back-end Services and just uses the Redis Scripts to interface with Redis directly. The source code also serves as a good demonstration of the declarative coding style that Templates encourages that in addition to being highly-readable requires orders of magnitude less code than our previous Redis JavaScript SPA's with a comparable feature-set.
Having a much smaller code-base makes it much easier to maintain and enhance whilst being less susceptible to becoming obsolete by the next new JavaScript framework as it would only require rewriting 75 lines of JavaScript instead of the complete rewrite that would be required to convert the existing JavaScript Apps to a use different JavaScript fx.
app.settings​
The app.settings
for Redis is similar to Web App Starter above except it adds a redis.connection
to configure a RedisManagerPool at the connection string provided as well as Redis Scripts to give Templates access to the Redis instance.
debug true
name Redis Web App
port 5000
contentRoot ~/../redis
webRoot ~/../redis
redis.connection localhost:6379
Rockwind​
source /Rockwind - demo rockwind-sqlite.web-app.io
The Rockwind website shows an example of combining multiple websites in a single Web App by having them separated in different folders - a Rock
stars Content Website and a dynamic data-driven UI for the Northwind
database which can run against either SQL Server, MySql or SQLite database using just configuration. It also includes Sharp APIs examples for rapidly developing Web APIs.
Rockstars​
/rockstars is an example of a Content Website that maintains multiple sub sections itself, each with their own layouts - /rockstars/alive for living Rockstars and /rockstars/dead for those that have died. Each Rockstar maintains their own encapsulated mix of HTML, markdown content and splash image that intuitively uses the closest _layout.html
, content.md
and splash.jpg
from the page they're referenced from. This approach makes it easy to move entire sub sections over by just moving a folder and it will automatically use the relevant layout and partials of its parent.
Northwind​
/northwind is an example of a dynamic UI for a database containing a form to filter results, multi-nested detail pages and deep-linking for quickly navigating between referenced data. Templates is also a great solution for rapidly developing Web APIs where the /api/customers.html API Page below:
{‎{ limit | default(100) | assignTo: limit }‎}
{‎{ 'select Id, CompanyName, ContactName, ContactTitle, City, Country from Customer' | assignTo: sql }‎}
{‎{ PathArgs | endIfEmpty | useFmt('{0} where Id = @id', sql)
| dbSingle({ id: PathArgs[0] })
| return }‎}
{‎{ id | endIfEmpty | use('Id = @id') | addTo: filters }‎}
{‎{ city | endIfEmpty | use('City = @city') | addTo: filters }‎}
{‎{ country | endIfEmpty | use('Country = @country') | addTo: filters }‎}
{‎{ filters | endIfEmpty | useFmt('{0} where {1}', sql, join(filters, ' and ')) | assignTo: sql }‎}
{‎{ sql | appendFmt(" ORDER BY CompanyName {0}", sqlLimit(limit)) | assignTo: sql }‎}
{‎{ sql | dbSelect({ id, city, country })
| return }‎}
Contains all the code needed to generate the following API endpoints:
/customers API | |
All Customers |
Accept HTTP Header also supported
|
---|---|
Alfreds Futterkiste Details | |
As List | |
Customers in Germany | |
Customers in London | |
Combination Query | /api/customers?city=London&country=UK&limit=3 |
Multi platform configurations​
In addition to being a .NET Core 2.0 App that runs flawlessly cross-platform on Windows, Linux and OSX, Web Apps can also support multiple RDBMS's and Virtual File Systems using just configuration:
The Web App can be run using different settings with:
dotnet web/app.dll ../rockwind/web.sqlserver.settings
Try Rockwind against your local RDBMS​
The /support/northwind-data project lets you quickly try out Rockwind against your local RDBMS by populating it with a copy of the Northwind database using the same sqlserver
identifier and connection string from the App's settings, e.g:
dotnet run sqlserver "Server=localhost;Database=northwind;User Id=test;Password=test;"
web.azure.settings​
The example Azure configuration is also configured to use a different Virtual File System where instead of sourcing Web App files from the filesystem they're sourced from an Azure Blob Container.
In this case we're not using any files from the App so we don't need to set a contentRoot
or webRoot
path. This also means for deployment we're just deploying the WebApp binary along with app.settings since both Web App files and database are sourced remotely from managed Azure services.
Upload Rockwind files to your Azure Blob Storage Container​
The /support/copy-files project lets you run Rockwind against your own Azure Blob Container by populating it with a copy of the /rockwind App's files using the same configuration in web.azure.settings:
dotnet run azure "{ConnectionString:$AZURE_BLOB_CONNECTION_STRING,ContainerName:rockwind}"
Multi-RDBMS SQL​
As Templates is unable to use a Typed ORM like OrmLite to hide the nuances of each database, we need to be more diligent when creating parameterized SQL that works across multiple databases by using the sql*
DB Scriptss to avoid using RDBMS-specific SQL syntax. The /northwind/customer.html contains a good example containing a number of things to watch out for:
{‎{ id | endIfEmpty | useFmt(
`select o.Id,
{0} Employee,
OrderDate, ShipCountry, ShippedDate,
{1} Total
from {2} o
inner join
OrderDetail d on o.Id = d.OrderId
inner join
Employee e on o.EmployeeId = e.Id
where CustomerId = @id
group by o.Id, EmployeeId, FirstName, LastName, OrderDate, ShipCountry, ShippedDate`,
sqlConcat(["e.FirstName", "' '", "e.LastName"]),
sqlCurrency("sum((d.Unitprice * d.Quantity) - d.discount)"),
sqlQuote("Order"))
| dbSelect({ id }) | assignTo: orders }‎}
Use sqlConcat
to concatenate strings using the RDBMS-specific SQL for the configured database. Likewise sqlCurrency
utilizes RDBMS-specific SQL functions to return monetary values in a currency format, whilst sqlQuote
is used for quoting tables and columns named after a reserved word.
Of course if you don't intend on supporting multiple RDBMS's, you can ignore this and use RDBMS-specific syntax.
Rockwind VFS​
source /rockwind-vfs - demo rockwind-aws.web-app.io
/rockwind-vfs is a clone of the Rockwind Web App with 3 differences: It uses the resolveAsset
filter for each .js
, .css
and image
web asset so that it's able to generate external URLs directly to the S3 Bucket, Azure Blob Container or CDN hosting a copy of the App's files where it both reduces the load on your Web App and maximize the responsiveness for the end user.
To maximize responsiveness when using remote storage, all embedded files also utilize caching:
{‎{ "content.md" | includeFileWithCache | markdown }‎}
The other difference is that each table and column has been quoted in "double-quotes" so that it works in PostgreSQL which otherwise treats unquoted symbols as lowercase. This version of Rockwind also works using SQL Server and SQLite as they also support "Table"
quotes but not MySql which uses Back Ticks
or [SquareBrackets]
. These differences makes it infeasible to develop Web Apps that support both PostgreSQL and MySql unless you're willing to use all lowercase, snake_case or the sqlQuote
filter for every table and column.
resolveAsset​
When using a remote file storage like AWS S3 or Azure Blob Storage it's a good idea to use the resolveAsset
filter for each external file reference. By default it returns the same path it was called with so it will continue to work locally but then ServiceStack effectively becomes a proxy where it calls the remote Storage Service for each requested download.
<link rel="stylesheet" href="{‎{ 'assets/css/bootstrap.css' | resolveAsset }‎}" />
<img src="{‎{ 'splash.jpg' | resolveAsset }‎}" id="splash" alt="Dave Grohl" />
ServiceStack asynchronously writes each file to the Response Stream with the last Last-Modified
HTTP Header to enable browser caching so it's still a workable solution but for optimal performance you can specify args.assetsBase
in your app.settings to populate the assetsBase TemplateContext
Argument the resolveAsset
filter uses to generate an external URL reference to the remote file, reducing the load and improving the performance of your App, especially when configured to use a CDN.
AWS Cloud Apps​
The AWS settings below shows an example of this where every external resource in rockwind-aws.web-app.io has been replaced with a direct reference to the asset on its configured S3 bucket:
Rockwind.Aws/app.settings​
# Note: values prefixed with '$' are resolved from Environment Variables
debug false
name AWS S3 PostgreSQL Web App
bind *
port 5000
db postgres
db.connection $AWS_RDS_POSTGRES
files s3
files.config {AccessKey:$AWS_ACCESS_KEY,SecretKey:$AWS_SECRET_KEY,Region:us-east-1,Bucket:rockwind}
args.assetsBase http://s3-postgresql.s3-website-us-east-1.amazonaws.com/
# Reduces an S3 API call, but takes longer for modified pages to appear
checkForModifiedPagesAfterSecs 60
defaultFileCacheExpirySecs 60
With all files being sourced from S3 and the App configured to use AWS RDS PostgreSQL the entire App is hosted on AWS's managed cloud services that's decoupled from the .NET Core 2.0 binary that runs it that for the most part won't require redeploying unless making configuration changes or upgrading the web/app.dll
as any App changes can just be uploaded straight to S3 where changes will reflected after checkForModifiedPagesAfterSecs
, which tells the Web App how long to wait before checking for file changes whilst defaultFileCacheExpirySecs
specifies how long to cache included files like content.md
.
DockerFile​
Deployments are also greatly simplified as all that's needed is to deploy the WebApp binary and the app.settings of your Cloud App, e.g. This is the DockerFile for rockwind-aws.web-app.io - deployed to AWS ECS using the deployment scripts in Rockwind.Aws as per our .NET Core Docker Deployment Guideline:
FROM microsoft/dotnet:2.0-sdk
COPY web /web
ADD https://raw.githubusercontent.com/NetCoreWebApps/rockwind-aws/master/app.settings /web/app.settings
WORKDIR /web
EXPOSE 5000/tcp
ENV ASPNETCORE_URLS https://*:5000
ENTRYPOINT ["dotnet", "/web/app.dll"]
Azure Cloud Apps​
We can also create Azure Cloud Apps in the same we've done above for AWS which we've deployed to rockwind.azurewebsites.net. It's running the same /rockwind-vfs Web App but using an Azure hosted SQL Server database and all its files are hosted on Azure Blob Storage:
Rockwind.Azure/app.settings​
# Note: values prefixed with '$' are resolved from Environment Variables
debug false
name Azure Blob SQL Server Web App
bind *
port 5000
db sqlserver
db.connection $AZURE_SQL_CONNECTION_STRING
files azure
files.config {ConnectionString:$AZURE_BLOB_CONNECTION_STRING,ContainerName:rockwind}
args.assetsBase https://servicestack.blob.core.windows.net/rockwind/
# Reduces an Azure Blob API call, but takes longer for modified pages to appear
checkForModifiedPagesAfterSecs 60
defaultFileCacheExpirySecs 60
See NetCoreWebApps/rockwind-azure for a detailed step-by-step guide for deploying Web Apps to Azure.
Plugins​
source /Plugins - demo plugins.web-app.io
Up till now the Apps above only have only used functionality built into ServiceStack, to enable even greater functionality but still retain all the benefits of developing Web Apps you can drop .dll's with custom functionality into your Web App's /plugins
folder. The plugins support in Web Apps is as frictionless as we could make it, there's no configuration to maintain or special interfaces to implement, you can just drop your existing implementation .dll's as-is into the App's /plugins
folder.
Plugins allow "no touch" sharing of ServiceStack Plugins, Services, Template Filters, Template Code Pages, Validators, etc. contained within .dll's or .exe's dropped in a Web App's /plugins folder which are auto-registered on startup. The source code for all plugins used in this App were built from the .NET Core 2.0 projects in the /example-plugins folder. The plugins.web-app.io Web App below walks through examples of using Custom Filters, Services and Validators:
Registering ServiceStack Plugins​
ServiceStack Plugins can be added to your App by listing it's Type Name in the features
config entry in app.settings:
debug true
name Web App Plugins
port 5000
contentRoot ~/../plugins
webRoot ~/../plugins
features CustomPlugin, OpenApiFeature, PostmanFeature, CorsFeature, ValidationFeature
CustomPlugin { ShowProcessLinks: true }
ValidationFeature { ScanAppHostAssemblies: true }
All plugins listed in features
will be added to your Web App's AppHost in the order they're specified. They can further customized by adding a separate config entry with the Plugin Name and a JavaScript Object literal to populate the Plugin at registration, e.g the config above is equivalent to:
Plugins.Add(new CustomPlugin { ShowProcessLinks = true });
Plugins.Add(new OpenApiFeature());
Plugins.Add(new PostmanFeature());
Plugins.Add(new CorsFeature());
Plugins.Add(new ValidationFeature { ScanAppHostAssemblies = true });
Custom Plugin​
In this case it tells our CustomPlugin from /plugins/ServerInfo.dll to also show Process Links in its /metadata Page:
public class CustomPlugin : IPlugin
{
public bool ShowDrivesLinks { get; set; } = true;
public bool ShowProcessLinks { get; set; }
public void Register(IAppHost appHost)
{
if (ShowDrivesLinks)
{
var diskFormat = Env.IsWindows ? "NTFS" : "ext2";
appHost.GetPlugin<MetadataFeature>()
.AddPluginLink("/drives", "All Disks")
.AddPluginLink($"/drives?DriveFormatIn={diskFormat}", $"{diskFormat} Disks");
}
if (ShowProcessLinks)
{
appHost.GetPlugin<MetadataFeature>()
.AddPluginLink("/processes", "All Processes")
.AddPluginLink("/process/current", "Current Process");
}
}
}
Where as it was first registered in the list will appear before any links registered by other plugins:
Chat​
source / - demo chat.web-app.io
/Chat is an example of the ultimate form of extensibility where instead of just being able to add Services, Filters and Plugins, etc. You can add your entire AppHost
which Web Apps will use instead of its own. This vastly expands the use-cases that can be built with Web Apps as it gives you complete fine-grained control over how your App is configured.
Develop back-end using .NET IDE's​
For chat.web-app.io we've taken a copy of the existing .NET Core 2.0 Chat App and moved its C# code to /example-plugins/Chat and its files to /Chat where it can be developed like any other Web App except it utilizes the Chat AppHost and implementation in the SelfHost Chat App.
Customizations from the original .NET Core Chat implementation includes removing MVC and Razor dependencies and configuration, extracting its _layout.html and converting index.html to use Templates from its original default.cshtml. It's also been enhanced with the ability to evaluate Templates from the Chat window, as seen in the screenshot above.
Chat AppHost​
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
var appSettings = new TextFileSettings("~/../../apps/chat/app.settings".MapProjectPath());
app.UseServiceStack(new AppHost(appSettings));
}
}
public class AppHost : AppHostBase
{
public AppHost() : base("Chat Web App", typeof(ServerEventsServices).GetAssembly()) {}
public AppHost(IAppSettings appSettings) : this() => AppSettings = appSettings;
public override void Configure(Container container)
{
Plugins.AddIfNotExists(new SharpPagesFeature()); //Already added if it's running as a Web App
Plugins.Add(new ServerEventsFeature());
SetConfig(new HostConfig
{
DefaultContentType = MimeTypes.Json,
AllowSessionIdsInHttpParams = true,
});
this.CustomErrorHttpHandlers.Remove(HttpStatusCode.Forbidden);
//Register all Authentication methods you want to enable for this web app.
Plugins.Add(new AuthFeature(
() => new AuthUserSession(),
new IAuthProvider[] {
new TwitterAuthProvider(AppSettings), //Sign-in with Twitter
new FacebookAuthProvider(AppSettings), //Sign-in with Facebook
new GithubAuthProvider(AppSettings), //Sign-in with GitHub
}));
container.RegisterAutoWiredAs<MemoryChatHistory, IChatHistory>();
Plugins.Add(new CorsFeature(
allowOriginWhitelist: new[] { "http://localhost", "http://null.jsbin.com" },
allowCredentials: true,
allowedHeaders: "Content-Type, Allow, Authorization"));
}
}
Reusing Web App's web.setting and files​
One nice thing from being able to reuse existing AppHost's is being able to develop all back-end C# Services and Custom Filters as a stand-alone .NET Core Project where it's more productive with access to .NET IDE tooling and debugging.
To account for these 2 modes we use AddIfNotExists
to only register the SharpPagesFeature
plugin
when running as a stand-alone App and add an additional constructor so it reuses the existing app.settings
as its IAppSettings provider for is custom App configuration like OAuth App keys required for enabling Sign-In's via with Twitter, Facebook and GitHub when running on http://localhost:5000
as seen in Chat's app.settings.
After the back-end has been implemented we can build and copy the compiled Chat.dll into the Chat's /plugins folder where we can take advantage of the improved development experience for rapidly developing its UI.
Simplified Web App Deployments​
Not having to build projects also greatly simplifies deployments as it just becomes an exercise of deploying your Web App's static files, app.settings
and the pre-compiled Web App binary. We're maintaining step-by-step guides for the most popular deployment methods in sharpscript.net/docs/deploying-sharp-apps which we'll continue adding to in future.
To give you an idea of how easy deployments are, after a one-time setup of supervisor and nginx configuration for each App we deploy all Web Apps above (excl AWS and Azure Cloud Apps which are deployed to AWS/Azure using Docker) in seconds by running this script below:
cat ../apps/bare/app.settings | sed "/debug/s/ .*/ false/" | sed "/port/s/ .*/ 5001/" > ../apps/web/web.bare.settings
cat ../apps/redis/app.settings | sed "/debug/s/ .*/ false/" | sed "/port/s/ .*/ 5002/" > ../apps/web/web.redis.settings
cat ../apps/rockwind/web.sqlite.settings | sed "/debug/s/ .*/ false/" | sed "/port/s/ .*/ 5003/" > ../apps/web/web.rockwind-sqlite.settings
cat ../apps/rockwind-vfs/web.sqlite.settings | sed "/debug/s/ .*/ false/" | sed "/port/s/ .*/ 5004/" > ../apps/web/web.rockwind-vfs-sqlite.settings
cat ../apps/plugins/app.settings | sed "/debug/s/ .*/ false/" | sed "/port/s/ .*/ 5005/" > ../apps/web/web.plugins.settings
cat ../apps/chat/web.release.settings | sed "/port/s/ .*/ 5006/" > ../apps/web/web.chat.settings
rsync -avz -e 'ssh' ../apps deploy@web-app.io:/home/deploy
ssh deploy@web-app.io "sudo supervisorctl restart all"
Which generates deployment app.settings
from each App's development app.settings by flipping the debug
flag and configuring each App to run on a different port. rsync
is then used to incrementally upload just the files that have changed before supervisorctl
is run remotely to restart all Web Apps.
If prefered you can choose to only restart the App that's changed by replacing the last line with:
sudo supervisorctl restart <app name>
If you're not running a Linux or OSX Desktop OS, this script can be run using Windows Subsystem for Linux (WSL).
Limitations​
Templates supports a very limited subset of JavaScript, namely all primitive data types, calling functions, bindings and the -
and !
unary operators. This current limitation is similar to LISP where named functions are used instead of operators. We're still undecided on whether to add support for more JavaScript operators as we'd prefer to have as minimal non-overridable behavior as possible and currently filters define all functionality in Templates.
Currently you'll need to use named Math functions to perform arithmetic operations, likewise for boolean logic or Boolean Expressions for filters that support it.
Current Status​
Whilst we expect there to be some bugs from our initial release the existing Apps and use-cases we've already developed with it have served as good tests cases to harden its implementation. Will continue adding more rigorous tests, complex expressions and example Apps in future releases.
To make it easy to compare Template to C# syntax we've also ported C#'s LINQ Examples where of all the languages we've ported, it's the most succinct and only interactive example.
Free for OSS and Commercial Projects​
We believe we've only just scratched the surface of what's possible with Templates and we'd love to see what new use-cases it can help achieve and help encourage an ecosystem of pluggable and reusable filters, that the core of Templates is being developed in ServiceStack.Common which is an unrestricted library that's free for OSS and commercial usage.
ServiceStack's existing free-quota restrictions only applies if you're using OrmLite, ServiceStack.Redis or exceed the allowed free-quota of ServiceStack Services.
Debug Inspector​
All ServiceStack Apps now have access to rich introspection and queryability for inspecting remote ServiceStack instances with the new Metadata Debug Inspector.
The Debug Template is a Service in SharpPagesFeature
that's pre-registered in DebugMode. The Service can also be available when not in DebugMode by enabling it with:
Plugins.Add(new SharpPagesFeature {
MetadataDebugAdminRole = RoleNames.Admin, // Only allow Admin users
})
This registers the Service but limits it to Users with the Admin
role, alternatively you configure an
Admin Secret:
SetConfig(new HostConfig { AdminAuthSecret = "secret" })
Which will let you access it by appending the authsecret to the querystring: /metadata/debug?authsecret=secret
Alternatively if preferred you can make the Debug Template Service available to all users with:
Plugins.Add(new SharpPagesFeature {
MetadataDebugAdminRole = RoleNames.AllowAnyUser, // Allow Authenticated Users
MetadataDebugAdminRole = RoleNames.AllowAnon, // Allow anyone
})
Which is the configuration that allows sharpscript.net//metadata/debug to be accessible to anyone.
JavaScript Utils​
The development of Templates also brought with it the development of a number of high-performance utilities that are useful for use on their own. The ServiceStack.Text JSON Serializer was only designed for serializing Typed POCOs, you can still use it to deserialize dynamic JSON but you would need to specify the Type to deserialize into on the call-site otherwise the value would be returned as a string.
Templates implementation of JavaScript preserves the Type which can be used to parse JavaScript or JSON literals:
JSON.parse("1") //= int 1
JSON.parse("1.1") //= double 1.1
JSON.parse("'a'") //= string "a"
JSON.parse("{a:1}") //= new Dictionary<string, object> { {"a", 1 } }
It can be used to parse dynamic JSON and any primitive JavaScript data type. The inverse API of JSON.stringify()
is also available.
Eval​
Eval is useful if you want to execute custom JavaScript functions, or if you want to have a text DSL or scripting language for executing custom logic or business rules you want to be able to change without having to compile or redeploy your App. It uses Templates Sandbox which lets you evaluate the script within a custom scope that defines what functions and arguments it has access to, e.g:
public class CustomFilter : TemplateFilter
{
public string reverse(string text) => new string(text.Reverse().ToArray());
}
var scope = JS.CreateScope(
args: new Dictionary<string, object> { { "arg", "value"} },
functions: new CustomFilter());
JS.eval("arg", scope) //= "value"
JS.eval("reverse(arg)", scope) //= "eulav"
JS.eval("itemsOf(3, padRight(reverse(arg), 8, '_'))", scope) //= ["eulav___", "eulav___", "eulav___"]
//= { a: ["eulav___", "eulav___", "eulav___"] }
JS.eval("{a: itemsOf(3, padRight(reverse(arg), 8, '_')) }", scope)
Simple Container​
In order for Templates to be free of external dependencies and be decoupled from any one Web Framework but still retain AutoWired functionality it uses a new SimpleContainer which implements IContainer - the smallest interface we could define for a minimal but useful IOC:
public interface IContainer
{
Func<object> CreateFactory(Type type);
IContainer AddSingleton(Type type, Func<object> factory);
IContainer AddTransient(Type type, Func<object> factory);
object Resolve(Type type);
bool Exists(Type type);
}
It's late-bound API supports registering dependencies in the 2 most useful Scopes: Singleton and Transient. Leveraging the utility of extension methods, every IOC implementing IContainer
also gains the same Typed Generic API, e.g:
container.AddTransient<IFoo,Foo>();
container.AddTransient<IFoo>(() => new Foo());
container.AddTransient<IBar>(() => new Bar());
container.AddTransient(() => new FooImpl());
container.AddTransient<FooImpl>();
container.AddSingleton(typeof(Foo));
container.AddSingleton(() => foo);
var foo = container.Resolve<IFoo>();
var bar = container.Resolve(typeof(IBar));
var hasFoo = container.Exists<IFoo>();
Both Funq.Container
and SimpleContainer
implement the IContainer
interface which
ServiceStack's SharpPagesFeature utilizes to replace the TemplateContext's built-in IOC to use Funq where it shares the same IOC instance and is able to resolve ServiceStack's AppHost dependencies.
Fast, small, dependency-free IOC​
We initially chose Funq
because it was the amongst the fastest, smallest and most embeddable IOC's available with a pleasant Typed API, we've since had several requests in the past to extract ServiceStack's enhanced version of Funq
so it can be used outside of ServiceStack.dll
but it would have resulted in unnecessary friction and overhead for little gain as Funq
is still usable outside of a ServiceStack App, it just required a reference to ServiceStack.dll
.
We're happy to report SimpleContainer
is even smaller and faster than Funq and only requires a dependency to ServiceStack.Common.dll
. It supports AutoWiring, constructor and public property injection but not Funq's other less used features like Child Containers, named dependencies and Request Scoped dependencies.
Simple AppSettings​
SimpleAppSettings is an IAppSettings provider that you can use to maintain substitutable App Configuration without a dependency to ServiceStack.dll
which can be populated with a string Dictionary:
AppSettings = new SimpleAppSettings(new Dictionary<string, string> {
["string"] = "value",
["EnableFeature.1"] = "true",
["AllowedUsers"] = "Tom,Mick,Harry",
}));
string value = AppSettings.GetString("string");
bool enableFeature1 = AppSettings.Get("EnableFeature.1", defaultValue:false);
bool enableFeature2 = AppSettings.Get("EnableFeature.2", defaultValue:false);
IList<string> allowedUsers = AppSettings.GetList("AllowedUsers");
Virtual File System​
The major change added in order for Templates to be isolated from the ServiceStack Web Framework was to decouple the
Virtual File System providers from ServiceStack's AppHost and move them
to ServiceStack.Common
.
This separation makes it easier to use VFS providers outside of ServiceStack AppHost which is a useful abstraction for copying files from different file sources as done in the copy-files project to upload files to AWS S3 or Azure Blob Storage.
AddVirtualFileSources​
Registering an additional VFS provider in AppHost's previously required overriding GetVirtualFileSources()
, they can now also be registered
by adding them to AddVirtualFileSources
, e.g:
AddVirtualFileSources.Add(vfsProvider);
VFS Breaking Change​
ServiceStack App's typically don't create instances of VFS providers directly but all VFS provider constructors needed to be changed to
remove its IAppHost
dependency. We used the same breaking change window to also give the user-facing VFS providers better names,
changing from *VirtualPathProvider
to *VirtualFiles
, e.g:
FileSystemVirtualFiles
MemoryVirtualFiles
ResourceVirtualFiles
S3VirtualFiles
AzureBlobVirtualFiles
MultiVirtualFiles
The VFS providers and extension methods in ServiceStack.Common
use the same ServiceStack.IO
namespace that the
VFS Interfaces are defined in where typically this would be the only change
required, including using ServiceStack.IO;
if you're using any VFS extension methods.
ServiceStack.Azure​
We've added deeper integration with Azure with ServiceStack.Azure - a new project containing Azure backed managed implementations for popular ServiceStack providers (as we've done with ServiceStack.Aws):
ServiceBusMqServer
- MQ Server for invoking ServiceStack Services via Azure ServiceBusAzureBlobVirtualFiles
- Virtual File System provider using Azure Blob StorageAzureTableCacheClient
- Cache Client provider using Azure Table Storage
We intend to add support for additional providers in future and make it even easier for ServiceStack Apps to be able to move freely between hosting on an Azure or an AWS managed infrastructure.
ServiceBus MQ Server​
Configuring to use ServiceBus is the same as other MQ Servers, by first registering the ServiceBus IMessageService
provider followed by registering all ServiceStack Services you want to be able to invoke via MQ's:
container.Register<IMessageService>(c => new ServiceBusMqServer(ConnectionString));
var mqServer = container.Resolve<IMessageService>();
mqServer.RegisterHandler<MyRequest>(ExecuteMessage);
AfterInitCallbacks.Add(appHost => mqServer.Start());
Azure Blob Storage VFS​
The AzureBlobVirtualFiles
VFS provider can be used to serve website content directly from an Azure Blob Storage container:
public class AppHost : AppHostBase
{
public override void Configure(Container container)
{
//Specify to use Azure Blob Container for uploading / writing files
VirtualFiles = new AzureBlobVirtualFiles(connectionString, containerName);
//Register an additional File Source for static files
AddVirtualFileSources.Add(VirtualFiles);
}
}
Azure Table Storage Cache Client​
The AzureTableCacheClient
Caching provider lets you use an Azure Table for your App's distributed caching:
container.Register<ICacheClient>(c => new AzureTableCacheClient(CacheConnectionString));
ServiceStack​
A number of internal improvements were made for making ServiceStack run better than ever on .NET Core:
Internal improvements​
In preparation for .NET Core's plans to disallow sync read / writes to Request and Responses
a number of internal handlers were refactored to use async APIs when writing to the Response Stream including static files and all raw
byte[]
, Stream
responses, including HTTP Partial Content responses.
Custom Results can implement the new IStreamWriterAsync
and IPartialWriterAsync
interfaces to return results that asynchronously writes to the Response Stream.
These interfaces should be used instead of the existing sync IStreamWriter
and IPartialWriter
interfaces which have been deprecated, although they're
safe to continue using in ASP.NET v4.5 and HttpListener self-hosts as it's very unlikely they'll ever have sync writes disabled by default.
ASP.NET, HttpListener and .NET Core hosts were refactored to use as much of the same code-paths as possible to ensure better consistency and code maintenance.
The HTTP Request Pipeline was refactored to only use VFS APIs when determining static file requests resulting in more consistent behavior for all VFS sources. It's also been updated to use minimal I/O requests for better performance when using remote storage VFS sources like AWS S3 or Azure Blob Storage.
Requests to directories are automatically redirected to enforce a trailing slash, it can be disabled with Config.RedirectDirectoriesToTrailingSlashes=false
.
Strict Mode​
We're adding a new Strict Mode to ServiceStack which you can use to make ServiceStack behave stricter and throw Exceptions when it sees certain failure conditions. To enable Strict Mode across all libraries use:
Env.StrictMode = true;
Otherwise to just enable StrictMode for ServiceStack:
SetConfig(new HostConfig {
StrictMode = true
})
When enabled ServiceStack will perform runtime checks to catch invalid state, currently:
- Checks if Services return Value Types
- Checks if UserSession has circular dependencies
- Fails fast for exceptions on Startup
It hasn't been expanded to other libraries yet, but in future we'll use it to change the default mode of deserializing as much as possible without error, to fail fast when it detects an error condition. Initially it will be used in Text Serializers and OrmLite to detect mapping errors.
Content-Type Specific Service Implementations​
Service implementations can now use Verb{Format}
method names to provide a different implementation for handling a specific Content-Type.
The Service below defines several different implementation for handling the same Request:
[Route("/my-request")]
public class MyRequest
{
public string Name { get; set; }
}
public class ContentTypeServices : Service
{
public object Any(MyRequest request) => ...; // Handles all other unspecified Verbs/Formats to /my-request
public object GetJson(MyRequest request) => ..; // Handles GET /my-request for JSON responses
public object AnyHtml(MyRequest request) => // Handles POST/PUT/DELETE/etc /my-request for HTML Responses
$@"<html>
<body>
<h1>AnyHtml {request.Name}</h1>
</body>
</html>";
public object GetHtml(MyRequest request) => // Handles GET /my-request for HTML Responses
$@"<html>
<body>
<h1>GetHtml {request.Name}</h1>
</body>
</html>";
}
This convention can be used for any of the formats listed in ContentTypes.KnownFormats
, which by default includes:
- json
- xml
- jsv
- csv
- html
- protobuf
- msgpack
- wire
Redirect Paths​
The RedirectPaths
dictionary can be used to maintain a redirect mapping of redirect paths, e.g. we use this to redirect
all requests to /metadata/
to redirect to /metadata
:
SetConfig(new HostConfig {
RedirectPaths = {
{ "/metadata/", "/metadata" },
}
})
Forbidden Paths​
The ForbiddenPaths
can be used to prevent access to different folders in your Web Root, e.g:
SetConfig(new HostConfig {
ForbiddenPaths = {
"/private-folder",
}
})
ServiceAssemblies​
The list of Service Implementation Assemblies specified in your AppHost constructor is available from IAppHost.ServiceAssemblies
which plugins can use to enable auto-wired features, e.g. you can use ScanAppHostAssemblies
in ValidationFeature
to automatically
register any validators defined in the Service Implementation Assemblies:
Plugins.Add(new ValidationFeature {
ScanAppHostAssemblies = true
})
ServiceStack Minor Features​
- New
Config.Metadata.GetAllDtos()
metadata API to return all DTO Types - Encrypted Messaging Requests are now marked as Secure in
IRequest.RequestAttributes
VaryByHeaders
option added to[CacheResponse]
attribute- New
[ExcludeMetadata]
attribute as alias for[Exclude(Feature.Metadata | Feature.Soap)]
- Added example for manually creating a JWT Token
Service Clients​
New *Body
and *BodyAsync
APIs have been added to all Service Clients which lets you post a separate Request Body for Request DTOs
that implement IRequiresRequestStream
where they contain both properties and a custom Request Body, e.g:
[Route("/json")]
public class SendJson : IRequiresRequestStream, IReturn<string>
{
public string Name { get; set; }
public Stream RequestStream { get; set; }
}
[Route("/text")]
public class SendText : IRequiresRequestStream, IReturn<string>
{
public string Name { get; set; }
public string ContentType { get; set; }
public Stream RequestStream { get; set; }
}
public class SendRawService : Service
{
[JsonOnly]
public object Any(SendJson request) => request.RequestStream.ReadFully();
public object Any(SendText request)
{
base.Request.ResponseContentType = request.ContentType ?? base.Request.AcceptTypes[0];
return request.RequestStream.ReadFully();
}
}
The new APIs accept both a Request DTO which specifies which Service to call and what properties to add to the QueryString and another object to send in the raw HTTP Request Body, e.g:
var client = new JsonServiceClient(BaseUrl);
var json = client.PostBody(new SendJson { Name = "JSON body" }, new PocoRequest { Foo = "Bar" });
json.FromJson<PocoRequest>().Foo //= Bar
json = await client.PutBodyAsync(new SendJson { Name = "JSON body" }, "{\"Foo\":\"Bar\"}");
json.FromJson<PocoRequest>().Foo //= Bar
var client = new JsonHttpClient(BaseUrl);
var request = new SendText { Name = "Text body", ContentType = "text/plain" };
var text = await client.PostBodyAsync(request, "foo");
text //= foo
- The new
ClientConfig.SkipEmptyArrays
option can be used to ignore empty arrays when generating urls
AutoQuery Changes​
Previously all AutoQuery Requests would execute an additional Aggregate query to return the total records available for that query. As this can be unnecessary overhead for requests that don't need it, we've made it opt-in where requests that need the total can add it on the QueryString, e.g:
/query?Include=Total
Or on the Request DTO:
var response = client.Get(new MyQuery { Include = "Total" });
You can restore the previous behavior and have the Total returned in every request with:
Plugins.Add(new AutoQueryFeature {
IncludeTotal = true
})
The 2 places where this is need in ServiceStack, the GetLazy
Service Client API and the Admin UI have been changed to include the total in each request.
- Empty Collections in AutoQuery are now ignored, the same as
null
collections.
Native Types​
User defined interfaces on Request DTOs are now being exported in the generated DTOs. It can be disabled with:
this.GetPlugin<NativeTypesFeature>().MetadataTypesConfig.ExcludeImplementedInterfaces = true;
- Support was also added for Arrays of Nullable Types.
Open API Refinements​
You can register Open API Tags by adding them to the Tags
collection:
Plugins.Add(new OpenApiFeature
{
Tags =
{
new OpenApiTag
{
Name = "TheTag",
Description = "TheTag Description",
ExternalDocs = new OpenApiExternalDocumentation
{
Description = "Link to External Docs Desc",
Url = "http://example.org/docs/path",
}
}
}
});
[ApiMember(IsRequired = true)]
is now included inOpenApiSchema
[ApiResponse(IsDefaultResponse = true)]
can be used to specify the default Service response- The
LogoHref
andLogoUrl
properties can be used to customize the/swagger-ui
logo - A
RequestType
was added inOpenApiOperation
to make it easy for filters to map Open API classes back to Services - Added support for
IReturnVoid
NoContent responses
Request Logging​
- Add logging for short-circuited requests terminated in Request Filters
- Allow logging of non-Service Requests, opt-in with
LimitToServiceRequests=false
- Add
SkipLogging
delegate to control which requests should be logged
ServiceStack.RabbitMq​
You can send MQ Request bodies using a different registered Content-Type which ServiceStack will use to deserialize into the Request DTO.
LiteDB Auth Provider​
Stefan de Vogelaere from the ServiceStack Community released the ServiceStack.Authentication.LiteDB AuthProvider for LiteDB - A .NET NoSQL Document Store in a single data file.
ServiceStack.Text​
Several enhancements were added in ServiceStack.Text to improve support for Object Dictionaries and KeyValuePair's which are extensively used in Templates, including support in CSV, QueryStrings and AutoMapping/Conversion Utils.
String Segment Extensions​
We use StringSegment
extensively for parsing strings as it avoids creating strings on the heap by returning a sliced view of the
original string. It's a new struct Type available in .NET Core which we include a polyfil of for .NET v4.5 in ServiceStack.Text.
We've further enhanced it with several StringSegment extension methods to make it easier to work with:
- ToStringSegment()
- IsNullOrEmpty()
- GetChar()
- IndexOf()
- IndexOfAny()
- LastIndexOf()
- LastIndexOfAny()
- Substring()
- Advance()
- Subsegment()
- SafeSubsegment()
- LeftPart()
- LastLeftPart()
- RightPart()
- LastRightPart()
- LastRightPart()
- SplitOnFirst()
- SplitOnLast()
- WithoutExtension()
- GetExtension()
- ParentDirectory()
- TrimEnd()
- EqualsIgnoreCase()
- StartsWith()
- StartsWithIgnoreCase()
- EndsWith()
TryParse*()
/Parse*()
- Several overloads for parsing different primitive types- TryReadPart()
- TryReadLine()
TryReadLine is particularly nice for efficiently reading lines from a large string without generating any string references on the heap:
var pos = 0;
var buf = new StringSegment(fileContents);
while (buf.TryReadLine(out StringSegment line, ref pos)) {
// line
}
Resolve Paths​
The ResolvePaths()
extension method evaluates string paths containing directory commands, e.g:
"/a/b/../".ResolvePaths() //= /a/
"/a/b/..".ResolvePaths() //= /a
"a/../b".ResolvePaths() //= b
"a/../b/./c".ResolvePaths() //= b/c
DynamicNumber​
DynamicNumber
provides a number of APIs for efficiently handling unknown object numeric Types which can be used
to perform arithmetic operations on unknown types:
object objInt = 1;
object objDouble = 1.1;
DynamicNumber.Add(objInt, objInt) //= int 2
DynamicNumber.Add(objInt, objDouble) //= double 2.1
DynamicNumber.Multiply('2', "1.1") //= double 2.2
It automatically upcasts to perform the operation on the appropriate number type. It also automatically parses strings and returns them in the best fitting common numeric type, e.g. int, long, ulong, double or decimal.
Or if preferred you can have numbers parsed into their best fitting numeric type with:
DynamicNumber.TryParseIntoBestFit("1", out object result) //= byte 1
JsConfig.TryParseIntoBestFit = true;
DynamicNumber.TryParse("1", out object result) //= byte 1
There's a Dynamic*
class for each of .NET's primitive number types. They can be used individually to force an operation to use a particular numeric type, e.g:
DynamicULong.Instance.add(obj1, obj2);
ServiceStack's AutoMapping generic object.ConvertTo<T>
extension method was refactored to use this more efficient implementation.
OrmLite​
SQL Server JSON​
@KevinHoward added preliminary support for SQL Server JSON queries, e.g:
var results = db.Select<Table>(q => Sql.JsonValue(q.JsonColumn, "$.State") == "NV" && q.Id == 1);
See JsonExpressionsTest.cs for more examples.
Requires
SqlServer2016Dialect.Provider
Normalizing PostgreSQL​
By default PostgreSQL's dialect provider uses quoted snake_case for all Table and Column names. It can be configured to generate similar SQL as other RDBMS's with:
PostgreSqlDialectProvider.Instance.Normalize = true;
Where it will use the default Naming strategy and only quote tables and columns using reserved words. OrmLite's mapping is case-insensitive so will still be able to map columns as a result of PostgreSQL's lowercase names for unquoted symbols.
Ignore properties​
The new [IgnoreOnInsert]
and [IgnoreOnUpdate]
attributes can be used to ignore properties from INSERT's and UPDATE's.
- Added
[Computed]
attribute as a better named alias for[Compute]
- Added support for using string params larger than default string length
- The new
SqlConcat
,SqlCurrency
,SqlBool
andSqlLimit
APIs can help creating cross-platform SQL, see SqlDialectTests.cs for examples.