AppHost Configuration

Anatomy of a ServiceStack AppHost

All ServiceStack App's are configured within an AppHost which typically consists of the following elements:

public class AppHost 
  : AppHostBase                       // Type of AppHost used, normally AppHostBase
{
    public AppHost() : base(
      "MyApp",                        // Service Name in Metadata Pages
      typeof(MyServices).Assembly) {} // The ServiceInterfaces Assemblies where all Services are located

    // Configure your AppHost with the necessary configuration and dependencies your App needs
    public override void Configure(Container container)
    {
        // Set Global AppHost Configuration
        base.SetConfig(new HostConfig
        {
            DebugMode = AppSettings.Get(nameof(HostConfig.DebugMode), false)
        });

        //Register IOC Dependencies
        container.Register<IRedisClientsManager>(c => new RedisManagerPool());

        // Add Plugins to extend your App with additional functionality
        Plugins.Add(new AutoQueryFeature { 
            MaxLimit = 100  // Feature specific configuration
        });
    }
}

INFO

DebugMode should be not be true when deployed to production.

Physical Project Structure

See Physical Project Structure docs to learn about ServiceStack's Solution layout.

Register AppHost in .NET Core

In ASP.NET Core, ServiceStack's AppHost is registered as middleware:

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
    //Register your ServiceStack AppHost as a .NET Core module
    app.UseServiceStack(new AppHost { 
        // Use ASP.NET Core configuration sources, e.g. **appsettings.json**
        AppSettings = new NetCoreAppSettings(Configuration) 
    }); 
}

See .NET Core docs for more info on using ServiceStack on .NET Core.

ConfigureAppHost

The same functionality used to configure your AppHost in Modular Startup configurations, also exists in the stand-alone HostContext.ConfigureAppHost() API which also lets you lazily configure your ServiceStack AppHost at any point before it's initialized:

HostContext.ConfigureAppHost(
    beforeConfigure: appHost => ...,
    afterConfigure: appHost => ...,
    afterPluginsLoaded: appHost => ...,
    afterAppHostInit: appHost => ...);

ConfigureOperation

The ConfigureOperation method has the same benefits for configuring an APIs metadata which is especially useful for AutoQuery AutoGen services since its operation metadata only exists after its default implementation is generated by AutoGen:

appHost.ConfigureOperation<Register>(op => op.FormLayout = new()
{
    Input.For<Register>(x => x.DisplayName,     x => x.Help = "Your first and last name"),
    Input.For<Register>(x => x.Email,           x => x.Type = Input.Types.Email),
    Input.For<Register>(x => x.Password,        x => x.Type = Input.Types.Password),
    Input.For<Register>(x => x.ConfirmPassword, x => x.Type = Input.Types.Password),
});

Which overrides the default Auto UI Form to use its custom layout:

Run AppHost in .NET Framework

new AppHost().Init();

IOC Registration

See IOC docs for an overview of the APIs and behavior of ServiceStack's built-in Funq IOC.

Modularizing Services

See Modularizing Services for info on Modularizing functionality in your AppHost.

Default HostConfig

The default configuration for ServiceStack's HostConfig, for the most up to date version see HostConfig.cs:

SetConfig(new HostConfig {
    WsdlServiceNamespace = "http://schemas.servicestack.net/types",
    ApiVersion = "1.0",
    EmbeddedResourceSources = new List<Assembly>(),
    EmbeddedResourceBaseTypes = new[] { HostContext.AppHost.GetType(), typeof(Service) }.ToList(),
    EmbeddedResourceTreatAsFiles = new HashSet<string>(),
    EnableAccessRestrictions = true,
    EnableAutoHtmlResponses = true,
    WebHostPhysicalPath = "~".MapServerPath(),
    HandlerFactoryPath = ServiceStackPath,
    MetadataRedirectPath = null,
    DefaultContentType = null,
    PreferredContentTypes = new List<string> {
        MimeTypes.Html, MimeTypes.Json, MimeTypes.Xml, MimeTypes.Jsv
    },
    AllowJsonpRequests = true,
    AllowRouteContentTypeExtensions = true,
    UseHttpOnlyCookies = true,  //default ;HttpOnly
    UseSecureCookies = true,    //default ;Secure (https)
    UseSameSiteCookies = false, //default ;SameSite=Lax
    DebugMode = false,
    StrictMode = Env.StrictMode,
    DefaultDocuments = new List<string> {
        "default.htm",
        "default.html",
        "default.cshtml",
        "default.md",
        "index.htm",
        "index.html",
        "default.aspx",
        "default.ashx",
    },
    GlobalResponseHeaders = new Dictionary<string, string> {
        { "Vary", "Accept" },
        { "X-Powered-By", Env.ServerUserAgent },
    },
    IsMobileRegex = new Regex("Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|(hpw|web)OS|Fennec|" 
                     + "Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune", RegexOptions.Compiled),
    RequestRules = new Dictionary<string, Func<IHttpRequest, bool>> {
        {"AcceptsHtml", req => req.Accept?.IndexOf(MimeTypes.Html, StringComparison.Ordinal) >= 0 },
        {"AcceptsJson", req => req.Accept?.IndexOf(MimeTypes.Json, StringComparison.Ordinal) >= 0 },
        {"AcceptsXml", req => req.Accept?.IndexOf(MimeTypes.Xml, StringComparison.Ordinal) >= 0 },
        {"AcceptsJsv", req => req.Accept?.IndexOf(MimeTypes.Jsv, StringComparison.Ordinal) >= 0 },
        {"AcceptsCsv", req => req.Accept?.IndexOf(MimeTypes.Csv, StringComparison.Ordinal) >= 0 },
        {"IsAuthenticated", req => req.IsAuthenticated() },
        {"IsMobile", req => Instance.IsMobileRegex.IsMatch(req.UserAgent) },
        {"{int}/**", req => int.TryParse(req.PathInfo.Substring(1).LeftPart('/'), out _) },
        {"path/{int}/**", req => {
            var afterFirst = req.PathInfo.Substring(1).RightPart('/');
            return !string.IsNullOrEmpty(afterFirst) && int.TryParse(afterFirst.LeftPart('/'), out _);
        }‎},
        {"**/{int}", req => int.TryParse(req.PathInfo.LastRightPart('/'), out _) },
        {"**/{int}/path", req => {
            var beforeLast = req.PathInfo.LastLeftPart('/');
            return !string.IsNullOrEmpty(beforeLast) && int.TryParse(beforeLast.LastRightPart('/'), out _);
        }‎},
    },
    IgnoreFormatsInMetadata = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {},
    AllowFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
    {
        "js", "ts", "tsx", "jsx", "css", "htm", "html", "shtm", "txt", "xml", "rss", "csv", "pdf",
        "jpg", "jpeg", "gif", "png", "bmp", "ico", "tif", "tiff", "svg",
        "avi", "divx", "m3u", "mov", "mp3", "mpeg", "mpg", "qt", "vob", "wav", "wma", "wmv",
        "flv", "swf", "xap", "xaml", "ogg", "ogv", "mp4", "webm", "eot", "ttf", "woff", "woff2", "map",
        "xls", "xla", "xlsx", "xltx", "doc", "dot", "docx", "dotx", "ppt", "pps", "ppa", "pptx", "potx", 
        "wasm"
    },
    CompressFilesWithExtensions = new HashSet<string>(),
    AllowFilePaths = new List<string>
    {
        "jspm_packages/**/*.json", //JSPM
        ".well-known/**/*",        //LetsEncrypt
    },
    ForbiddenPaths = new List<string>(),
    DebugAspNetHostEnvironment = Env.IsMono ? "FastCGI" : "IIS7",
    DebugHttpListenerHostEnvironment = Env.IsMono ? "XSP" : "WebServer20",
    EnableFeatures = Feature.All,
    WriteErrorsToResponse = true,
    ReturnsInnerException = true,
    DisposeDependenciesAfterUse = true,
    LogUnobservedTaskExceptions = true,
    HtmlReplaceTokens = new Dictionary<string, string>(),
    AddMaxAgeForStaticMimeTypes = new Dictionary<string, TimeSpan> {
        { "image/gif", TimeSpan.FromHours(1) },
        { "image/png", TimeSpan.FromHours(1) },
        { "image/jpeg", TimeSpan.FromHours(1) },
    },
    AppendUtf8CharsetOnContentTypes = new HashSet<string> { MimeTypes.Json, },
    RouteNamingConventions = new List<RouteNamingConventionDelegate> {
        RouteNamingConvention.WithRequestDtoName,
        RouteNamingConvention.WithMatchingAttributes,
        RouteNamingConvention.WithMatchingPropertyNames
    },
    MapExceptionToStatusCode = new Dictionary<Type, int>(),
    UseSaltedHash = false,
    FallbackPasswordHashers = new List<IPasswordHasher>(),
    AllowSessionIdsInHttpParams = false,
    AllowSessionCookies = true,
    RestrictAllCookiesToDomain = null,
    DefaultJsonpCacheExpiration = new TimeSpan(0, 20, 0),
    MetadataVisibility = RequestAttributes.Any,
    Return204NoContentForEmptyResponse = true,
    AllowJsConfig = true,
    AllowPartialResponses = true,
    AllowAclUrlReservation = true,
    AddRedirectParamsToQueryString = false,
    RedirectToDefaultDocuments = false,
    RedirectDirectoriesToTrailingSlashes = true,
    StripApplicationVirtualPath = false,
    ScanSkipPaths = new List<string> {
        "obj/",
        "bin/",
        "node_modules/",
        "jspm_packages/",
        "bower_components/",
        "wwwroot_build/",
        "wwwroot/", // only in .NET Framework
    },
    RedirectPaths = new Dictionary<string, string>
    {
        { "/metadata/", "/metadata" },
    },
    IgnoreWarningsOnPropertyNames = new List<string> {
        Keywords.Format, Keywords.Callback, Keywords.Debug, Keywords.AuthSecret, Keywords.JsConfig,
        Keywords.IgnorePlaceHolder, Keywords.Version, Keywords.VersionAbbr, Keywords.Version.ToPascalCase(),
        Keywords.ApiKeyParam, Keywords.Code, Keywords.Redirect, Keywords.Continue, "s", "f"
    },
    XmlWriterSettings = new XmlWriterSettings
    {
        Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false),
    },
    FallbackRestPath = null,
    UseHttpsLinks = false,
    UseJsObject = true,
    EnableOptimizations = true,
    UseCamelCase = true, // only in .NET Core; otherwise false
});

Configure Logging

To ensure every ServiceStack service uses the same Global Logger it should be configured before ServiceStack's AppHost is initialized, e.g:

LogManager.LogFactory = new ConsoleLogFactory(); 
new AppHost().Init();

See Logging docs for info on the various logging providers available.

Testing

See Unit and Integration Testing docs for testing with ServiceStack.