Repository files navigation

Starcounter.Startup

Dependency injection for Starcounter 2.4

Table of contents

Table of contents generated with markdown-toc

Installation

This package is available on nuget. You can get it there. To install with CLI run:

Install-Package Starcounter.Startup

Getting started

Create a startup class (commonly called Startup). It has to implement Starcounter.Startup.Abstractions.IStartup.

usingMicrosoft.Extensions.DependencyInjection;usingStarcounter.Startup.Abstractions;publicclassStartup:IStartup{publicvoidConfigureServices(IServiceCollectionservices){// here you can configure your DI container}publicvoidConfigure(IApplicationBuilderapplicationBuilder){// here you perform any start-up tasks for your application// applicationBuilder can be used to access the IServiceProvider}}

IStartup mimics asp.net core's startup concept. You can read about it on docs.microsoft.com In your Program.cs you should have only a single call to the bootstrapper:

usingStarcounter.Startup;publicclassProgram{publicstaticvoidMain(){DefaultStarcounterBootstrapper.Start(newStartup());}}

This will start your application according to the Startup class.

Router

Router is a class that helps creating the view-models and register them with URIs. To use it you have to add it to the DI container in ConfigureServices:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();}

Registering view-models with UrlAttribute

You can annotate your view-model with Starcounter.Startup.Routing.UrlAttribute:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs")][Url("/DogsApp/AllDogs")]// you can use UrlAttribute multiple timespublicpartialclassDogViewModel:Json{// ...}

⚠️ Be careful to use UrlAttribute from Starcounter.Startup.Routing namespace. Common mistake is to use one from System.Runtime.Remoting.Activation instead.

You have to tell the Router to scan your assembly for all view-models to register them:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().RegisterAllFromCurrentAssembly();// see also RegisterAllFromAssembly() if you keep your view-models in a separate project}

The snippets above will register a handler under "/DogsApp/Dogs" and "/DogsApp/AllDogs". This handler will return DogViewModel.

Exposing view-models to blending and browser

By default, applying [UrlAttribute] will expose your view-model to the browser (or anyone who uses HTTP) under the supplied URI, and to the Blending Engine under the partial URI.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs/{?}")]publicpartialclassDogViewModel:Json{// ...}

The code above will expose your view-model under /DogsApp/Dogs/{?} to the browser, and /DogsApp/partial/Dogs/{?} to the Blending Engine. You won't be able to call the second URI from the browser.

You can also expose your view-model to Blending or browser only.

// blending only[Url("/DogsApp/Dogs/{?}",External=false)]// browser only[Url("/DogsApp/Dogs/{?}",Blendable=false)]

Registering view-models manually

Sometimes you want to have more control over the registration of your handlers. You can achieve that with manual registration:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().HandleGet<DogViewModel>("/DogsApp/very-custom-uri/Dogs",newHandlerOptions{SelfOnly=true});// see also other overloads of HandleGet}

The snippet above will register a handler under "/DogsApp/very-custom-uri/Dogs". This handler will return DogViewModel, but will only be available to Blending engine.

Handling URI parameters, working with Context

In most cases when you use URI parameters, they correspond to a database entity of type T and your view-model implements IBound<T>. This case, illustrated below, is handled automatically:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{// ...}

In the example below, if you open URI /DogsApp/Dog/123, then:

  • Router (more specifically, ContextMiddleware) will look for a Dog with id 123 in the database
  • If it's not found, then 404 response will be returned
  • If it's found, then DogViewModel will be initialized, and then its Data property will be populated with the Dog found in the database

This is usually the desired behavior and if it satisfies you, can skip the rest of this chapter.

To understand how this works you first need to know what Context is. Context is the data represented by URI arguments. If the URI has a Dog id a parameter 123 (/DogsApp/Dog/123), then Context is the Dog entity with id 123.

If the view-model implements IBound<T>, then the type of the Context is inferred to be T. You can, however override this by implementing IPageContext<T> interface. Consider following example:

You want the following view-model to be accessible by ID of the Dog. i.e. accessing /DogsApp/Dog/123/Owner should initialize DogOwnerViewModel.Data to the owner of the Dog with id 123.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}/Owner")]publicpartialclassDogOwnerViewModel:Json,IBound<Person>,IPageContext<Dog>{publicvoidHandleContext(Dogcontext){this.Data=context.Owner;}// ...}

Here, the context type would've been inferred to Person, but we explicitly declared it to be Dog. To implement the interface we also had to specify what happens with the context. If you don't implement this interface, but implement IBound<T>, the context is simply assigned to Data property.

But how is this context object fetched? By default, if the URI has only one parameter and Context is a database entity, the parameter value is used to fetch the Context from the database. However, if one of those conditions are not met or you want to override the default behavior, you must use [UriToContext]:

// using Starcounter.Startup.Routing;// using Starcounter.Linq;[Url("/DogsApp/DogByName/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{[UriToContext]// the name of this method is irrelevant, but calling it UriToContext is a good practicepublicstaticDogUriToContext(string[]args,IDogsRepositorydogsRepository){// args is guaranteed to have one element, because its only URI has only one parameter// returning null will cause the Router to respond with 404returndogsRepository.GetByName(args[0]);}// ...}

In this example instead of fetching the Context by its ID, we fetch it using its Name property. To use [UriToContext], apply it to one method that:

  • is public static
  • has return type assignable to Context type
  • has the first parameter of type string[]

This method will be invoked before the view-model is created. It will be passed URI parameters as its sole argument. If it returns null, the Router will respond with 404. Otherwise, the return value will be used as the Context. This method can accept more than one parameter. Any additional parameters will be treated as a dependency and resolved using Dependency Injection container.

[UriToContext] and IPageMiddleware<T> features are connected, but independent. You can use them both or just one them.

Middleware

Sometimes you want to define a behavior that will be applied to all the requests processed by the Router. To achieve this, implement Starcounter.Startup.Routing.IPageMiddleware interface and register it in the DI container.

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter;usingStarcounter.Startup.Routing;publicclassLoggingMiddleware:IPageMiddleware{privatereadonlyILogger<LoggingMiddleware>_logger;publicLoggingMiddleware(ILogger<LoggingMiddleware>logger){_logger=logger;}publicResponseRun(RoutingInforoutingInfo,Func<Response>next){_logger.LogInformation("Processing request");returnnext();}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();services.AddTransient<IPageMiddleware,LoggingMiddleware>();}

The above snippet will register LoggingMiddleware to run at every request processed by the Router.

AddRouter extension method mentioned before adds two pieces of middleware by default: MasterPageMiddleware and ContextMiddleware. If you want to prevent that behavior you can do that by passing false to includeDefaultMiddleware parameter:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter(false);// you can add them manually:// services.AddTransient<IPageMiddleware, MasterPageMiddleware>();// services.AddTransient<IPageMiddleware, ContextMiddleware>();}

Blending, Db.Scope, MasterPage

By default, every view-model you register with [Url] is both page URI and partial URI registered. When you access its page URI, Router creates a Db.Scope and retrieves its blending URI. This means, that if you request your view-model in the browser, the response can contain other, blended view-models as well. Router makes sure that they all share a common transaction.

A common application feature is to have some layout that wraps every response of an app and adds navigation features. This wrapping page would be called a master page. To enable it, create a view-model deriving from MasterPageBase and register it using SetMasterPage<T>:

{
"Html": "/MyApplication/views/MasterNavigation.html",
"InnerJson": {}
}
usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}}
<template><dom-bind><templateis="dom-bind"><h1>Application-wide header</h1><ahref="/MyApplication/Home">Go home</a><starcounter-includeview-model="{{model.InnerJson}}"></starcounter-include></template></dom-bind></template>
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage<MasterNavigationPage>();}

Controlling transaction scopes in master page

Without master page, all the blended view-models share a common transaction. With a master page like one defined above, all the blended view-models share a common transaction, but the master page itself has no transaction. You can change that if you want.

To put the master page in a transaction, you have to create it in Db.Scope. To do it, register your custom master page factory:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage((provider,routingInfo)=>Db.Scope(()=>newMasterNavigationPage()))}

The code above will create MasterNavigationPage in a transaction scope, but it will not be shared with the blended view-models. If you want it to be in shared transaction, you can change it by overriding ExecuteInScope in your master page:

usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}publicoverrideTExecuteInScope(Func<T>innerJsonFactory){returnAttachedScope.Scope(innerJsonFactory);}}

The code above will attach the blended view-models to the scope of the master page. That way all the view-models will share a transaction.

Dependency injection in view-models

To use services from the DI container in your view-model, declare a constructor that accepts dependencies as arguments. For more information about Dependency Injection, consult microsoft docs on DI.

publicpartialclassDogViewModel:Json{publicDogViewModel(IDogServicedogService){_dogService=dogService;}}

For a long time, Starcounter didn't support constructor injection, and used IInitPageWithDependncies marker interface instead. You would implement it and create public, non-static, void Init method that accepted your dependencies as parameters. Below is an example of that practice. It can be now safely converted to constructor injection.

// using Starcounter.Startup.Routing.Activation;// LEGACY CODEpublicpartialclassDogViewModel:Json,IInitPageWithDependencies{publicvoidInit(IDogServicedogService){_dogService=dogService;}}

⚠️Only view-models created by the Router (those i.e. created automatically by accessing a URI) will have their dependncies filled. View-models nested inside other view-model, that are created by Starcounter, will not automatically be created with dependencies.

// WON'T WORK// AllDogsViewModel.json{"Children":[{}]}// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){Children.Data=dogService.GetAllDogs();}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{// this won't even compilepublicChildViewModel(IDogServicedogService){// ...}}}

To fill dependencies for a nested view-model you have to create it by hand:

// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){foreach(vardogindogService.GetAllDogs()){Children.Add().Init(dogService);}}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{publicvoidInit(IDogServicedogService){// ...}}}

Services registered by default

DefaultStarcounterBootstrapper registers two aspnet.core's features by default - logging and options. You can read about them on docs.microsoft.com. All logs are printed on Standard Output by default.

UriHelper

UriHelper is a collection of static methods which ease working with Starcounter URIs. It exposes following methods. Each method below is accompanied by an example with output.

publicstaticstringPartialToPage(stringpartialUri)

Converts partial URI to page URI. E.g. PartialToPage("/MyApp/partial/dog") will return "/MyApp/dog".

publicstaticstringPageToPartial(stringpageUri)

Converts page URI to partial URI. E.g. PageToPartial("/MyApp/dog") will return "/MyApp/partial/dog".

publicstaticboolIsPartialUri(stringuri)

Returns true if the supplied URI is a partial URI. E.g. IsPartialUri("/MyApp/partial/dog") will return true, but IsPartialUri("/MyApp/dog") will return false.

publicstaticstringWithArguments(stringuriTemplate,paramsstring[]arguments)

Returns the supplied URI with its arguments filled. E.g. WithArguments("/MyApp/partial/dog/{?}", "xyz") will return "/MyApp/partial/dog/xyz".

Startup Filters

Usually when you want some code to execute during the startup of the application, you just put it in Configure method of your startup class. However, there's a second way to achieve it: define a class implementing IStartupFilter interface and register it in ConfigureServices. Below is a sample startup filter and a snippet to register it:

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter.Startup.Abstractions;namespaceStarcounter.Authorization.Authentication{publicclassLoggingStartupFilter:IStartupFilter{privatereadonlyILogger<LoggingStartupFilter>_logger;publicLoggingStartupFilter(ILogger<LoggingStartupFilter>logger){_logger=logger;}publicAction<IApplicationBuilder>Configure(Action<IApplicationBuilder>next){return app =>{_logger.LogInformation("Application started");next(app);};}}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;publicvoidConfigureServices(IServiceCollectionservices){services.AddTransient<IStartupFilter,LoggingStartupFilter>();}

This feature is especially useful in libraries, which do not directly control application's startup.

About

Bootstrapping library for 2.4 applications

Resources

Stars

0 stars

Watchers

18 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Starcounter.Startup

Dependency injection for Starcounter 2.4

Table of contents

Table of contents generated with markdown-toc

Installation

This package is available on nuget. You can get it there. To install with CLI run:

Install-Package Starcounter.Startup

Getting started

Create a startup class (commonly called Startup). It has to implement Starcounter.Startup.Abstractions.IStartup.

usingMicrosoft.Extensions.DependencyInjection;usingStarcounter.Startup.Abstractions;publicclassStartup:IStartup{publicvoidConfigureServices(IServiceCollectionservices){// here you can configure your DI container}publicvoidConfigure(IApplicationBuilderapplicationBuilder){// here you perform any start-up tasks for your application// applicationBuilder can be used to access the IServiceProvider}}

IStartup mimics asp.net core's startup concept. You can read about it on docs.microsoft.com In your Program.cs you should have only a single call to the bootstrapper:

usingStarcounter.Startup;publicclassProgram{publicstaticvoidMain(){DefaultStarcounterBootstrapper.Start(newStartup());}}

This will start your application according to the Startup class.

Router

Router is a class that helps creating the view-models and register them with URIs. To use it you have to add it to the DI container in ConfigureServices:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();}

Registering view-models with UrlAttribute

You can annotate your view-model with Starcounter.Startup.Routing.UrlAttribute:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs")][Url("/DogsApp/AllDogs")]// you can use UrlAttribute multiple timespublicpartialclassDogViewModel:Json{// ...}

⚠️ Be careful to use UrlAttribute from Starcounter.Startup.Routing namespace. Common mistake is to use one from System.Runtime.Remoting.Activation instead.

You have to tell the Router to scan your assembly for all view-models to register them:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().RegisterAllFromCurrentAssembly();// see also RegisterAllFromAssembly() if you keep your view-models in a separate project}

The snippets above will register a handler under "/DogsApp/Dogs" and "/DogsApp/AllDogs". This handler will return DogViewModel.

Exposing view-models to blending and browser

By default, applying [UrlAttribute] will expose your view-model to the browser (or anyone who uses HTTP) under the supplied URI, and to the Blending Engine under the partial URI.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs/{?}")]publicpartialclassDogViewModel:Json{// ...}

The code above will expose your view-model under /DogsApp/Dogs/{?} to the browser, and /DogsApp/partial/Dogs/{?} to the Blending Engine. You won't be able to call the second URI from the browser.

You can also expose your view-model to Blending or browser only.

// blending only[Url("/DogsApp/Dogs/{?}",External=false)]// browser only[Url("/DogsApp/Dogs/{?}",Blendable=false)]

Registering view-models manually

Sometimes you want to have more control over the registration of your handlers. You can achieve that with manual registration:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().HandleGet<DogViewModel>("/DogsApp/very-custom-uri/Dogs",newHandlerOptions{SelfOnly=true});// see also other overloads of HandleGet}

The snippet above will register a handler under "/DogsApp/very-custom-uri/Dogs". This handler will return DogViewModel, but will only be available to Blending engine.

Handling URI parameters, working with Context

In most cases when you use URI parameters, they correspond to a database entity of type T and your view-model implements IBound<T>. This case, illustrated below, is handled automatically:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{// ...}

In the example below, if you open URI /DogsApp/Dog/123, then:

  • Router (more specifically, ContextMiddleware) will look for a Dog with id 123 in the database
  • If it's not found, then 404 response will be returned
  • If it's found, then DogViewModel will be initialized, and then its Data property will be populated with the Dog found in the database

This is usually the desired behavior and if it satisfies you, can skip the rest of this chapter.

To understand how this works you first need to know what Context is. Context is the data represented by URI arguments. If the URI has a Dog id a parameter 123 (/DogsApp/Dog/123), then Context is the Dog entity with id 123.

If the view-model implements IBound<T>, then the type of the Context is inferred to be T. You can, however override this by implementing IPageContext<T> interface. Consider following example:

You want the following view-model to be accessible by ID of the Dog. i.e. accessing /DogsApp/Dog/123/Owner should initialize DogOwnerViewModel.Data to the owner of the Dog with id 123.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}/Owner")]publicpartialclassDogOwnerViewModel:Json,IBound<Person>,IPageContext<Dog>{publicvoidHandleContext(Dogcontext){this.Data=context.Owner;}// ...}

Here, the context type would've been inferred to Person, but we explicitly declared it to be Dog. To implement the interface we also had to specify what happens with the context. If you don't implement this interface, but implement IBound<T>, the context is simply assigned to Data property.

But how is this context object fetched? By default, if the URI has only one parameter and Context is a database entity, the parameter value is used to fetch the Context from the database. However, if one of those conditions are not met or you want to override the default behavior, you must use [UriToContext]:

// using Starcounter.Startup.Routing;// using Starcounter.Linq;[Url("/DogsApp/DogByName/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{[UriToContext]// the name of this method is irrelevant, but calling it UriToContext is a good practicepublicstaticDogUriToContext(string[]args,IDogsRepositorydogsRepository){// args is guaranteed to have one element, because its only URI has only one parameter// returning null will cause the Router to respond with 404returndogsRepository.GetByName(args[0]);}// ...}

In this example instead of fetching the Context by its ID, we fetch it using its Name property. To use [UriToContext], apply it to one method that:

  • is public static
  • has return type assignable to Context type
  • has the first parameter of type string[]

This method will be invoked before the view-model is created. It will be passed URI parameters as its sole argument. If it returns null, the Router will respond with 404. Otherwise, the return value will be used as the Context. This method can accept more than one parameter. Any additional parameters will be treated as a dependency and resolved using Dependency Injection container.

[UriToContext] and IPageMiddleware<T> features are connected, but independent. You can use them both or just one them.

Middleware

Sometimes you want to define a behavior that will be applied to all the requests processed by the Router. To achieve this, implement Starcounter.Startup.Routing.IPageMiddleware interface and register it in the DI container.

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter;usingStarcounter.Startup.Routing;publicclassLoggingMiddleware:IPageMiddleware{privatereadonlyILogger<LoggingMiddleware>_logger;publicLoggingMiddleware(ILogger<LoggingMiddleware>logger){_logger=logger;}publicResponseRun(RoutingInforoutingInfo,Func<Response>next){_logger.LogInformation("Processing request");returnnext();}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();services.AddTransient<IPageMiddleware,LoggingMiddleware>();}

The above snippet will register LoggingMiddleware to run at every request processed by the Router.

AddRouter extension method mentioned before adds two pieces of middleware by default: MasterPageMiddleware and ContextMiddleware. If you want to prevent that behavior you can do that by passing false to includeDefaultMiddleware parameter:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter(false);// you can add them manually:// services.AddTransient<IPageMiddleware, MasterPageMiddleware>();// services.AddTransient<IPageMiddleware, ContextMiddleware>();}

Blending, Db.Scope, MasterPage

By default, every view-model you register with [Url] is both page URI and partial URI registered. When you access its page URI, Router creates a Db.Scope and retrieves its blending URI. This means, that if you request your view-model in the browser, the response can contain other, blended view-models as well. Router makes sure that they all share a common transaction.

A common application feature is to have some layout that wraps every response of an app and adds navigation features. This wrapping page would be called a master page. To enable it, create a view-model deriving from MasterPageBase and register it using SetMasterPage<T>:

{
"Html": "/MyApplication/views/MasterNavigation.html",
"InnerJson": {}
}
usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}}
<template><dom-bind><templateis="dom-bind"><h1>Application-wide header</h1><ahref="/MyApplication/Home">Go home</a><starcounter-includeview-model="{{model.InnerJson}}"></starcounter-include></template></dom-bind></template>
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage<MasterNavigationPage>();}

Controlling transaction scopes in master page

Without master page, all the blended view-models share a common transaction. With a master page like one defined above, all the blended view-models share a common transaction, but the master page itself has no transaction. You can change that if you want.

To put the master page in a transaction, you have to create it in Db.Scope. To do it, register your custom master page factory:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage((provider,routingInfo)=>Db.Scope(()=>newMasterNavigationPage()))}

The code above will create MasterNavigationPage in a transaction scope, but it will not be shared with the blended view-models. If you want it to be in shared transaction, you can change it by overriding ExecuteInScope in your master page:

usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}publicoverrideTExecuteInScope(Func<T>innerJsonFactory){returnAttachedScope.Scope(innerJsonFactory);}}

The code above will attach the blended view-models to the scope of the master page. That way all the view-models will share a transaction.

Dependency injection in view-models

To use services from the DI container in your view-model, declare a constructor that accepts dependencies as arguments. For more information about Dependency Injection, consult microsoft docs on DI.

publicpartialclassDogViewModel:Json{publicDogViewModel(IDogServicedogService){_dogService=dogService;}}

For a long time, Starcounter didn't support constructor injection, and used IInitPageWithDependncies marker interface instead. You would implement it and create public, non-static, void Init method that accepted your dependencies as parameters. Below is an example of that practice. It can be now safely converted to constructor injection.

// using Starcounter.Startup.Routing.Activation;// LEGACY CODEpublicpartialclassDogViewModel:Json,IInitPageWithDependencies{publicvoidInit(IDogServicedogService){_dogService=dogService;}}

⚠️Only view-models created by the Router (those i.e. created automatically by accessing a URI) will have their dependncies filled. View-models nested inside other view-model, that are created by Starcounter, will not automatically be created with dependencies.

// WON'T WORK// AllDogsViewModel.json{"Children":[{}]}// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){Children.Data=dogService.GetAllDogs();}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{// this won't even compilepublicChildViewModel(IDogServicedogService){// ...}}}

To fill dependencies for a nested view-model you have to create it by hand:

// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){foreach(vardogindogService.GetAllDogs()){Children.Add().Init(dogService);}}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{publicvoidInit(IDogServicedogService){// ...}}}

Services registered by default

DefaultStarcounterBootstrapper registers two aspnet.core's features by default - logging and options. You can read about them on docs.microsoft.com. All logs are printed on Standard Output by default.

UriHelper

UriHelper is a collection of static methods which ease working with Starcounter URIs. It exposes following methods. Each method below is accompanied by an example with output.

publicstaticstringPartialToPage(stringpartialUri)

Converts partial URI to page URI. E.g. PartialToPage("/MyApp/partial/dog") will return "/MyApp/dog".

publicstaticstringPageToPartial(stringpageUri)

Converts page URI to partial URI. E.g. PageToPartial("/MyApp/dog") will return "/MyApp/partial/dog".

publicstaticboolIsPartialUri(stringuri)

Returns true if the supplied URI is a partial URI. E.g. IsPartialUri("/MyApp/partial/dog") will return true, but IsPartialUri("/MyApp/dog") will return false.

publicstaticstringWithArguments(stringuriTemplate,paramsstring[]arguments)

Returns the supplied URI with its arguments filled. E.g. WithArguments("/MyApp/partial/dog/{?}", "xyz") will return "/MyApp/partial/dog/xyz".

Startup Filters

Usually when you want some code to execute during the startup of the application, you just put it in Configure method of your startup class. However, there's a second way to achieve it: define a class implementing IStartupFilter interface and register it in ConfigureServices. Below is a sample startup filter and a snippet to register it:

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter.Startup.Abstractions;namespaceStarcounter.Authorization.Authentication{publicclassLoggingStartupFilter:IStartupFilter{privatereadonlyILogger<LoggingStartupFilter>_logger;publicLoggingStartupFilter(ILogger<LoggingStartupFilter>logger){_logger=logger;}publicAction<IApplicationBuilder>Configure(Action<IApplicationBuilder>next){return app =>{_logger.LogInformation("Application started");next(app);};}}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;publicvoidConfigureServices(IServiceCollectionservices){services.AddTransient<IStartupFilter,LoggingStartupFilter>();}

This feature is especially useful in libraries, which do not directly control application's startup.

About

Bootstrapping library for 2.4 applications

Resources

Stars

0 stars

Watchers

18 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Starcounter.Startup

Dependency injection for Starcounter 2.4

Table of contents

Table of contents generated with markdown-toc

Installation

This package is available on nuget. You can get it there. To install with CLI run:

Install-Package Starcounter.Startup

Getting started

Create a startup class (commonly called Startup). It has to implement Starcounter.Startup.Abstractions.IStartup.

usingMicrosoft.Extensions.DependencyInjection;usingStarcounter.Startup.Abstractions;publicclassStartup:IStartup{publicvoidConfigureServices(IServiceCollectionservices){// here you can configure your DI container}publicvoidConfigure(IApplicationBuilderapplicationBuilder){// here you perform any start-up tasks for your application// applicationBuilder can be used to access the IServiceProvider}}

IStartup mimics asp.net core's startup concept. You can read about it on docs.microsoft.com In your Program.cs you should have only a single call to the bootstrapper:

usingStarcounter.Startup;publicclassProgram{publicstaticvoidMain(){DefaultStarcounterBootstrapper.Start(newStartup());}}

This will start your application according to the Startup class.

Router

Router is a class that helps creating the view-models and register them with URIs. To use it you have to add it to the DI container in ConfigureServices:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();}

Registering view-models with UrlAttribute

You can annotate your view-model with Starcounter.Startup.Routing.UrlAttribute:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs")][Url("/DogsApp/AllDogs")]// you can use UrlAttribute multiple timespublicpartialclassDogViewModel:Json{// ...}

⚠️ Be careful to use UrlAttribute from Starcounter.Startup.Routing namespace. Common mistake is to use one from System.Runtime.Remoting.Activation instead.

You have to tell the Router to scan your assembly for all view-models to register them:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().RegisterAllFromCurrentAssembly();// see also RegisterAllFromAssembly() if you keep your view-models in a separate project}

The snippets above will register a handler under "/DogsApp/Dogs" and "/DogsApp/AllDogs". This handler will return DogViewModel.

Exposing view-models to blending and browser

By default, applying [UrlAttribute] will expose your view-model to the browser (or anyone who uses HTTP) under the supplied URI, and to the Blending Engine under the partial URI.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs/{?}")]publicpartialclassDogViewModel:Json{// ...}

The code above will expose your view-model under /DogsApp/Dogs/{?} to the browser, and /DogsApp/partial/Dogs/{?} to the Blending Engine. You won't be able to call the second URI from the browser.

You can also expose your view-model to Blending or browser only.

// blending only[Url("/DogsApp/Dogs/{?}",External=false)]// browser only[Url("/DogsApp/Dogs/{?}",Blendable=false)]

Registering view-models manually

Sometimes you want to have more control over the registration of your handlers. You can achieve that with manual registration:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().HandleGet<DogViewModel>("/DogsApp/very-custom-uri/Dogs",newHandlerOptions{SelfOnly=true});// see also other overloads of HandleGet}

The snippet above will register a handler under "/DogsApp/very-custom-uri/Dogs". This handler will return DogViewModel, but will only be available to Blending engine.

Handling URI parameters, working with Context

In most cases when you use URI parameters, they correspond to a database entity of type T and your view-model implements IBound<T>. This case, illustrated below, is handled automatically:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{// ...}

In the example below, if you open URI /DogsApp/Dog/123, then:

  • Router (more specifically, ContextMiddleware) will look for a Dog with id 123 in the database
  • If it's not found, then 404 response will be returned
  • If it's found, then DogViewModel will be initialized, and then its Data property will be populated with the Dog found in the database

This is usually the desired behavior and if it satisfies you, can skip the rest of this chapter.

To understand how this works you first need to know what Context is. Context is the data represented by URI arguments. If the URI has a Dog id a parameter 123 (/DogsApp/Dog/123), then Context is the Dog entity with id 123.

If the view-model implements IBound<T>, then the type of the Context is inferred to be T. You can, however override this by implementing IPageContext<T> interface. Consider following example:

You want the following view-model to be accessible by ID of the Dog. i.e. accessing /DogsApp/Dog/123/Owner should initialize DogOwnerViewModel.Data to the owner of the Dog with id 123.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}/Owner")]publicpartialclassDogOwnerViewModel:Json,IBound<Person>,IPageContext<Dog>{publicvoidHandleContext(Dogcontext){this.Data=context.Owner;}// ...}

Here, the context type would've been inferred to Person, but we explicitly declared it to be Dog. To implement the interface we also had to specify what happens with the context. If you don't implement this interface, but implement IBound<T>, the context is simply assigned to Data property.

But how is this context object fetched? By default, if the URI has only one parameter and Context is a database entity, the parameter value is used to fetch the Context from the database. However, if one of those conditions are not met or you want to override the default behavior, you must use [UriToContext]:

// using Starcounter.Startup.Routing;// using Starcounter.Linq;[Url("/DogsApp/DogByName/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{[UriToContext]// the name of this method is irrelevant, but calling it UriToContext is a good practicepublicstaticDogUriToContext(string[]args,IDogsRepositorydogsRepository){// args is guaranteed to have one element, because its only URI has only one parameter// returning null will cause the Router to respond with 404returndogsRepository.GetByName(args[0]);}// ...}

In this example instead of fetching the Context by its ID, we fetch it using its Name property. To use [UriToContext], apply it to one method that:

  • is public static
  • has return type assignable to Context type
  • has the first parameter of type string[]

This method will be invoked before the view-model is created. It will be passed URI parameters as its sole argument. If it returns null, the Router will respond with 404. Otherwise, the return value will be used as the Context. This method can accept more than one parameter. Any additional parameters will be treated as a dependency and resolved using Dependency Injection container.

[UriToContext] and IPageMiddleware<T> features are connected, but independent. You can use them both or just one them.

Middleware

Sometimes you want to define a behavior that will be applied to all the requests processed by the Router. To achieve this, implement Starcounter.Startup.Routing.IPageMiddleware interface and register it in the DI container.

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter;usingStarcounter.Startup.Routing;publicclassLoggingMiddleware:IPageMiddleware{privatereadonlyILogger<LoggingMiddleware>_logger;publicLoggingMiddleware(ILogger<LoggingMiddleware>logger){_logger=logger;}publicResponseRun(RoutingInforoutingInfo,Func<Response>next){_logger.LogInformation("Processing request");returnnext();}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();services.AddTransient<IPageMiddleware,LoggingMiddleware>();}

The above snippet will register LoggingMiddleware to run at every request processed by the Router.

AddRouter extension method mentioned before adds two pieces of middleware by default: MasterPageMiddleware and ContextMiddleware. If you want to prevent that behavior you can do that by passing false to includeDefaultMiddleware parameter:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter(false);// you can add them manually:// services.AddTransient<IPageMiddleware, MasterPageMiddleware>();// services.AddTransient<IPageMiddleware, ContextMiddleware>();}

Blending, Db.Scope, MasterPage

By default, every view-model you register with [Url] is both page URI and partial URI registered. When you access its page URI, Router creates a Db.Scope and retrieves its blending URI. This means, that if you request your view-model in the browser, the response can contain other, blended view-models as well. Router makes sure that they all share a common transaction.

A common application feature is to have some layout that wraps every response of an app and adds navigation features. This wrapping page would be called a master page. To enable it, create a view-model deriving from MasterPageBase and register it using SetMasterPage<T>:

{
"Html": "/MyApplication/views/MasterNavigation.html",
"InnerJson": {}
}
usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}}
<template><dom-bind><templateis="dom-bind"><h1>Application-wide header</h1><ahref="/MyApplication/Home">Go home</a><starcounter-includeview-model="{{model.InnerJson}}"></starcounter-include></template></dom-bind></template>
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage<MasterNavigationPage>();}

Controlling transaction scopes in master page

Without master page, all the blended view-models share a common transaction. With a master page like one defined above, all the blended view-models share a common transaction, but the master page itself has no transaction. You can change that if you want.

To put the master page in a transaction, you have to create it in Db.Scope. To do it, register your custom master page factory:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage((provider,routingInfo)=>Db.Scope(()=>newMasterNavigationPage()))}

The code above will create MasterNavigationPage in a transaction scope, but it will not be shared with the blended view-models. If you want it to be in shared transaction, you can change it by overriding ExecuteInScope in your master page:

usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}publicoverrideTExecuteInScope(Func<T>innerJsonFactory){returnAttachedScope.Scope(innerJsonFactory);}}

The code above will attach the blended view-models to the scope of the master page. That way all the view-models will share a transaction.

Dependency injection in view-models

To use services from the DI container in your view-model, declare a constructor that accepts dependencies as arguments. For more information about Dependency Injection, consult microsoft docs on DI.

publicpartialclassDogViewModel:Json{publicDogViewModel(IDogServicedogService){_dogService=dogService;}}

For a long time, Starcounter didn't support constructor injection, and used IInitPageWithDependncies marker interface instead. You would implement it and create public, non-static, void Init method that accepted your dependencies as parameters. Below is an example of that practice. It can be now safely converted to constructor injection.

// using Starcounter.Startup.Routing.Activation;// LEGACY CODEpublicpartialclassDogViewModel:Json,IInitPageWithDependencies{publicvoidInit(IDogServicedogService){_dogService=dogService;}}

⚠️Only view-models created by the Router (those i.e. created automatically by accessing a URI) will have their dependncies filled. View-models nested inside other view-model, that are created by Starcounter, will not automatically be created with dependencies.

// WON'T WORK// AllDogsViewModel.json{"Children":[{}]}// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){Children.Data=dogService.GetAllDogs();}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{// this won't even compilepublicChildViewModel(IDogServicedogService){// ...}}}

To fill dependencies for a nested view-model you have to create it by hand:

// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){foreach(vardogindogService.GetAllDogs()){Children.Add().Init(dogService);}}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{publicvoidInit(IDogServicedogService){// ...}}}

Services registered by default

DefaultStarcounterBootstrapper registers two aspnet.core's features by default - logging and options. You can read about them on docs.microsoft.com. All logs are printed on Standard Output by default.

UriHelper

UriHelper is a collection of static methods which ease working with Starcounter URIs. It exposes following methods. Each method below is accompanied by an example with output.

publicstaticstringPartialToPage(stringpartialUri)

Converts partial URI to page URI. E.g. PartialToPage("/MyApp/partial/dog") will return "/MyApp/dog".

publicstaticstringPageToPartial(stringpageUri)

Converts page URI to partial URI. E.g. PageToPartial("/MyApp/dog") will return "/MyApp/partial/dog".

publicstaticboolIsPartialUri(stringuri)

Returns true if the supplied URI is a partial URI. E.g. IsPartialUri("/MyApp/partial/dog") will return true, but IsPartialUri("/MyApp/dog") will return false.

publicstaticstringWithArguments(stringuriTemplate,paramsstring[]arguments)

Returns the supplied URI with its arguments filled. E.g. WithArguments("/MyApp/partial/dog/{?}", "xyz") will return "/MyApp/partial/dog/xyz".

Startup Filters

Usually when you want some code to execute during the startup of the application, you just put it in Configure method of your startup class. However, there's a second way to achieve it: define a class implementing IStartupFilter interface and register it in ConfigureServices. Below is a sample startup filter and a snippet to register it:

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter.Startup.Abstractions;namespaceStarcounter.Authorization.Authentication{publicclassLoggingStartupFilter:IStartupFilter{privatereadonlyILogger<LoggingStartupFilter>_logger;publicLoggingStartupFilter(ILogger<LoggingStartupFilter>logger){_logger=logger;}publicAction<IApplicationBuilder>Configure(Action<IApplicationBuilder>next){return app =>{_logger.LogInformation("Application started");next(app);};}}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;publicvoidConfigureServices(IServiceCollectionservices){services.AddTransient<IStartupFilter,LoggingStartupFilter>();}

This feature is especially useful in libraries, which do not directly control application's startup.

About

Bootstrapping library for 2.4 applications

Resources

Stars

0 stars

Watchers

18 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Starcounter.Startup

Dependency injection for Starcounter 2.4

Table of contents

Table of contents generated with markdown-toc

Installation

This package is available on nuget. You can get it there. To install with CLI run:

Install-Package Starcounter.Startup

Getting started

Create a startup class (commonly called Startup). It has to implement Starcounter.Startup.Abstractions.IStartup.

usingMicrosoft.Extensions.DependencyInjection;usingStarcounter.Startup.Abstractions;publicclassStartup:IStartup{publicvoidConfigureServices(IServiceCollectionservices){// here you can configure your DI container}publicvoidConfigure(IApplicationBuilderapplicationBuilder){// here you perform any start-up tasks for your application// applicationBuilder can be used to access the IServiceProvider}}

IStartup mimics asp.net core's startup concept. You can read about it on docs.microsoft.com In your Program.cs you should have only a single call to the bootstrapper:

usingStarcounter.Startup;publicclassProgram{publicstaticvoidMain(){DefaultStarcounterBootstrapper.Start(newStartup());}}

This will start your application according to the Startup class.

Router

Router is a class that helps creating the view-models and register them with URIs. To use it you have to add it to the DI container in ConfigureServices:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();}

Registering view-models with UrlAttribute

You can annotate your view-model with Starcounter.Startup.Routing.UrlAttribute:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs")][Url("/DogsApp/AllDogs")]// you can use UrlAttribute multiple timespublicpartialclassDogViewModel:Json{// ...}

⚠️ Be careful to use UrlAttribute from Starcounter.Startup.Routing namespace. Common mistake is to use one from System.Runtime.Remoting.Activation instead.

You have to tell the Router to scan your assembly for all view-models to register them:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().RegisterAllFromCurrentAssembly();// see also RegisterAllFromAssembly() if you keep your view-models in a separate project}

The snippets above will register a handler under "/DogsApp/Dogs" and "/DogsApp/AllDogs". This handler will return DogViewModel.

Exposing view-models to blending and browser

By default, applying [UrlAttribute] will expose your view-model to the browser (or anyone who uses HTTP) under the supplied URI, and to the Blending Engine under the partial URI.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs/{?}")]publicpartialclassDogViewModel:Json{// ...}

The code above will expose your view-model under /DogsApp/Dogs/{?} to the browser, and /DogsApp/partial/Dogs/{?} to the Blending Engine. You won't be able to call the second URI from the browser.

You can also expose your view-model to Blending or browser only.

// blending only[Url("/DogsApp/Dogs/{?}",External=false)]// browser only[Url("/DogsApp/Dogs/{?}",Blendable=false)]

Registering view-models manually

Sometimes you want to have more control over the registration of your handlers. You can achieve that with manual registration:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().HandleGet<DogViewModel>("/DogsApp/very-custom-uri/Dogs",newHandlerOptions{SelfOnly=true});// see also other overloads of HandleGet}

The snippet above will register a handler under "/DogsApp/very-custom-uri/Dogs". This handler will return DogViewModel, but will only be available to Blending engine.

Handling URI parameters, working with Context

In most cases when you use URI parameters, they correspond to a database entity of type T and your view-model implements IBound<T>. This case, illustrated below, is handled automatically:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{// ...}

In the example below, if you open URI /DogsApp/Dog/123, then:

  • Router (more specifically, ContextMiddleware) will look for a Dog with id 123 in the database
  • If it's not found, then 404 response will be returned
  • If it's found, then DogViewModel will be initialized, and then its Data property will be populated with the Dog found in the database

This is usually the desired behavior and if it satisfies you, can skip the rest of this chapter.

To understand how this works you first need to know what Context is. Context is the data represented by URI arguments. If the URI has a Dog id a parameter 123 (/DogsApp/Dog/123), then Context is the Dog entity with id 123.

If the view-model implements IBound<T>, then the type of the Context is inferred to be T. You can, however override this by implementing IPageContext<T> interface. Consider following example:

You want the following view-model to be accessible by ID of the Dog. i.e. accessing /DogsApp/Dog/123/Owner should initialize DogOwnerViewModel.Data to the owner of the Dog with id 123.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}/Owner")]publicpartialclassDogOwnerViewModel:Json,IBound<Person>,IPageContext<Dog>{publicvoidHandleContext(Dogcontext){this.Data=context.Owner;}// ...}

Here, the context type would've been inferred to Person, but we explicitly declared it to be Dog. To implement the interface we also had to specify what happens with the context. If you don't implement this interface, but implement IBound<T>, the context is simply assigned to Data property.

But how is this context object fetched? By default, if the URI has only one parameter and Context is a database entity, the parameter value is used to fetch the Context from the database. However, if one of those conditions are not met or you want to override the default behavior, you must use [UriToContext]:

// using Starcounter.Startup.Routing;// using Starcounter.Linq;[Url("/DogsApp/DogByName/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{[UriToContext]// the name of this method is irrelevant, but calling it UriToContext is a good practicepublicstaticDogUriToContext(string[]args,IDogsRepositorydogsRepository){// args is guaranteed to have one element, because its only URI has only one parameter// returning null will cause the Router to respond with 404returndogsRepository.GetByName(args[0]);}// ...}

In this example instead of fetching the Context by its ID, we fetch it using its Name property. To use [UriToContext], apply it to one method that:

  • is public static
  • has return type assignable to Context type
  • has the first parameter of type string[]

This method will be invoked before the view-model is created. It will be passed URI parameters as its sole argument. If it returns null, the Router will respond with 404. Otherwise, the return value will be used as the Context. This method can accept more than one parameter. Any additional parameters will be treated as a dependency and resolved using Dependency Injection container.

[UriToContext] and IPageMiddleware<T> features are connected, but independent. You can use them both or just one them.

Middleware

Sometimes you want to define a behavior that will be applied to all the requests processed by the Router. To achieve this, implement Starcounter.Startup.Routing.IPageMiddleware interface and register it in the DI container.

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter;usingStarcounter.Startup.Routing;publicclassLoggingMiddleware:IPageMiddleware{privatereadonlyILogger<LoggingMiddleware>_logger;publicLoggingMiddleware(ILogger<LoggingMiddleware>logger){_logger=logger;}publicResponseRun(RoutingInforoutingInfo,Func<Response>next){_logger.LogInformation("Processing request");returnnext();}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();services.AddTransient<IPageMiddleware,LoggingMiddleware>();}

The above snippet will register LoggingMiddleware to run at every request processed by the Router.

AddRouter extension method mentioned before adds two pieces of middleware by default: MasterPageMiddleware and ContextMiddleware. If you want to prevent that behavior you can do that by passing false to includeDefaultMiddleware parameter:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter(false);// you can add them manually:// services.AddTransient<IPageMiddleware, MasterPageMiddleware>();// services.AddTransient<IPageMiddleware, ContextMiddleware>();}

Blending, Db.Scope, MasterPage

By default, every view-model you register with [Url] is both page URI and partial URI registered. When you access its page URI, Router creates a Db.Scope and retrieves its blending URI. This means, that if you request your view-model in the browser, the response can contain other, blended view-models as well. Router makes sure that they all share a common transaction.

A common application feature is to have some layout that wraps every response of an app and adds navigation features. This wrapping page would be called a master page. To enable it, create a view-model deriving from MasterPageBase and register it using SetMasterPage<T>:

{
"Html": "/MyApplication/views/MasterNavigation.html",
"InnerJson": {}
}
usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}}
<template><dom-bind><templateis="dom-bind"><h1>Application-wide header</h1><ahref="/MyApplication/Home">Go home</a><starcounter-includeview-model="{{model.InnerJson}}"></starcounter-include></template></dom-bind></template>
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage<MasterNavigationPage>();}

Controlling transaction scopes in master page

Without master page, all the blended view-models share a common transaction. With a master page like one defined above, all the blended view-models share a common transaction, but the master page itself has no transaction. You can change that if you want.

To put the master page in a transaction, you have to create it in Db.Scope. To do it, register your custom master page factory:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage((provider,routingInfo)=>Db.Scope(()=>newMasterNavigationPage()))}

The code above will create MasterNavigationPage in a transaction scope, but it will not be shared with the blended view-models. If you want it to be in shared transaction, you can change it by overriding ExecuteInScope in your master page:

usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}publicoverrideTExecuteInScope(Func<T>innerJsonFactory){returnAttachedScope.Scope(innerJsonFactory);}}

The code above will attach the blended view-models to the scope of the master page. That way all the view-models will share a transaction.

Dependency injection in view-models

To use services from the DI container in your view-model, declare a constructor that accepts dependencies as arguments. For more information about Dependency Injection, consult microsoft docs on DI.

publicpartialclassDogViewModel:Json{publicDogViewModel(IDogServicedogService){_dogService=dogService;}}

For a long time, Starcounter didn't support constructor injection, and used IInitPageWithDependncies marker interface instead. You would implement it and create public, non-static, void Init method that accepted your dependencies as parameters. Below is an example of that practice. It can be now safely converted to constructor injection.

// using Starcounter.Startup.Routing.Activation;// LEGACY CODEpublicpartialclassDogViewModel:Json,IInitPageWithDependencies{publicvoidInit(IDogServicedogService){_dogService=dogService;}}

⚠️Only view-models created by the Router (those i.e. created automatically by accessing a URI) will have their dependncies filled. View-models nested inside other view-model, that are created by Starcounter, will not automatically be created with dependencies.

// WON'T WORK// AllDogsViewModel.json{"Children":[{}]}// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){Children.Data=dogService.GetAllDogs();}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{// this won't even compilepublicChildViewModel(IDogServicedogService){// ...}}}

To fill dependencies for a nested view-model you have to create it by hand:

// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){foreach(vardogindogService.GetAllDogs()){Children.Add().Init(dogService);}}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{publicvoidInit(IDogServicedogService){// ...}}}

Services registered by default

DefaultStarcounterBootstrapper registers two aspnet.core's features by default - logging and options. You can read about them on docs.microsoft.com. All logs are printed on Standard Output by default.

UriHelper

UriHelper is a collection of static methods which ease working with Starcounter URIs. It exposes following methods. Each method below is accompanied by an example with output.

publicstaticstringPartialToPage(stringpartialUri)

Converts partial URI to page URI. E.g. PartialToPage("/MyApp/partial/dog") will return "/MyApp/dog".

publicstaticstringPageToPartial(stringpageUri)

Converts page URI to partial URI. E.g. PageToPartial("/MyApp/dog") will return "/MyApp/partial/dog".

publicstaticboolIsPartialUri(stringuri)

Returns true if the supplied URI is a partial URI. E.g. IsPartialUri("/MyApp/partial/dog") will return true, but IsPartialUri("/MyApp/dog") will return false.

publicstaticstringWithArguments(stringuriTemplate,paramsstring[]arguments)

Returns the supplied URI with its arguments filled. E.g. WithArguments("/MyApp/partial/dog/{?}", "xyz") will return "/MyApp/partial/dog/xyz".

Startup Filters

Usually when you want some code to execute during the startup of the application, you just put it in Configure method of your startup class. However, there's a second way to achieve it: define a class implementing IStartupFilter interface and register it in ConfigureServices. Below is a sample startup filter and a snippet to register it:

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter.Startup.Abstractions;namespaceStarcounter.Authorization.Authentication{publicclassLoggingStartupFilter:IStartupFilter{privatereadonlyILogger<LoggingStartupFilter>_logger;publicLoggingStartupFilter(ILogger<LoggingStartupFilter>logger){_logger=logger;}publicAction<IApplicationBuilder>Configure(Action<IApplicationBuilder>next){return app =>{_logger.LogInformation("Application started");next(app);};}}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;publicvoidConfigureServices(IServiceCollectionservices){services.AddTransient<IStartupFilter,LoggingStartupFilter>();}

This feature is especially useful in libraries, which do not directly control application's startup.

About

Bootstrapping library for 2.4 applications

Resources

Stars

0 stars

Watchers

18 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Starcounter.Startup

Dependency injection for Starcounter 2.4

Table of contents

Table of contents generated with markdown-toc

Installation

This package is available on nuget. You can get it there. To install with CLI run:

Install-Package Starcounter.Startup

Getting started

Create a startup class (commonly called Startup). It has to implement Starcounter.Startup.Abstractions.IStartup.

usingMicrosoft.Extensions.DependencyInjection;usingStarcounter.Startup.Abstractions;publicclassStartup:IStartup{publicvoidConfigureServices(IServiceCollectionservices){// here you can configure your DI container}publicvoidConfigure(IApplicationBuilderapplicationBuilder){// here you perform any start-up tasks for your application// applicationBuilder can be used to access the IServiceProvider}}

IStartup mimics asp.net core's startup concept. You can read about it on docs.microsoft.com In your Program.cs you should have only a single call to the bootstrapper:

usingStarcounter.Startup;publicclassProgram{publicstaticvoidMain(){DefaultStarcounterBootstrapper.Start(newStartup());}}

This will start your application according to the Startup class.

Router

Router is a class that helps creating the view-models and register them with URIs. To use it you have to add it to the DI container in ConfigureServices:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();}

Registering view-models with UrlAttribute

You can annotate your view-model with Starcounter.Startup.Routing.UrlAttribute:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs")][Url("/DogsApp/AllDogs")]// you can use UrlAttribute multiple timespublicpartialclassDogViewModel:Json{// ...}

⚠️ Be careful to use UrlAttribute from Starcounter.Startup.Routing namespace. Common mistake is to use one from System.Runtime.Remoting.Activation instead.

You have to tell the Router to scan your assembly for all view-models to register them:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().RegisterAllFromCurrentAssembly();// see also RegisterAllFromAssembly() if you keep your view-models in a separate project}

The snippets above will register a handler under "/DogsApp/Dogs" and "/DogsApp/AllDogs". This handler will return DogViewModel.

Exposing view-models to blending and browser

By default, applying [UrlAttribute] will expose your view-model to the browser (or anyone who uses HTTP) under the supplied URI, and to the Blending Engine under the partial URI.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs/{?}")]publicpartialclassDogViewModel:Json{// ...}

The code above will expose your view-model under /DogsApp/Dogs/{?} to the browser, and /DogsApp/partial/Dogs/{?} to the Blending Engine. You won't be able to call the second URI from the browser.

You can also expose your view-model to Blending or browser only.

// blending only[Url("/DogsApp/Dogs/{?}",External=false)]// browser only[Url("/DogsApp/Dogs/{?}",Blendable=false)]

Registering view-models manually

Sometimes you want to have more control over the registration of your handlers. You can achieve that with manual registration:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().HandleGet<DogViewModel>("/DogsApp/very-custom-uri/Dogs",newHandlerOptions{SelfOnly=true});// see also other overloads of HandleGet}

The snippet above will register a handler under "/DogsApp/very-custom-uri/Dogs". This handler will return DogViewModel, but will only be available to Blending engine.

Handling URI parameters, working with Context

In most cases when you use URI parameters, they correspond to a database entity of type T and your view-model implements IBound<T>. This case, illustrated below, is handled automatically:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{// ...}

In the example below, if you open URI /DogsApp/Dog/123, then:

  • Router (more specifically, ContextMiddleware) will look for a Dog with id 123 in the database
  • If it's not found, then 404 response will be returned
  • If it's found, then DogViewModel will be initialized, and then its Data property will be populated with the Dog found in the database

This is usually the desired behavior and if it satisfies you, can skip the rest of this chapter.

To understand how this works you first need to know what Context is. Context is the data represented by URI arguments. If the URI has a Dog id a parameter 123 (/DogsApp/Dog/123), then Context is the Dog entity with id 123.

If the view-model implements IBound<T>, then the type of the Context is inferred to be T. You can, however override this by implementing IPageContext<T> interface. Consider following example:

You want the following view-model to be accessible by ID of the Dog. i.e. accessing /DogsApp/Dog/123/Owner should initialize DogOwnerViewModel.Data to the owner of the Dog with id 123.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}/Owner")]publicpartialclassDogOwnerViewModel:Json,IBound<Person>,IPageContext<Dog>{publicvoidHandleContext(Dogcontext){this.Data=context.Owner;}// ...}

Here, the context type would've been inferred to Person, but we explicitly declared it to be Dog. To implement the interface we also had to specify what happens with the context. If you don't implement this interface, but implement IBound<T>, the context is simply assigned to Data property.

But how is this context object fetched? By default, if the URI has only one parameter and Context is a database entity, the parameter value is used to fetch the Context from the database. However, if one of those conditions are not met or you want to override the default behavior, you must use [UriToContext]:

// using Starcounter.Startup.Routing;// using Starcounter.Linq;[Url("/DogsApp/DogByName/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{[UriToContext]// the name of this method is irrelevant, but calling it UriToContext is a good practicepublicstaticDogUriToContext(string[]args,IDogsRepositorydogsRepository){// args is guaranteed to have one element, because its only URI has only one parameter// returning null will cause the Router to respond with 404returndogsRepository.GetByName(args[0]);}// ...}

In this example instead of fetching the Context by its ID, we fetch it using its Name property. To use [UriToContext], apply it to one method that:

  • is public static
  • has return type assignable to Context type
  • has the first parameter of type string[]

This method will be invoked before the view-model is created. It will be passed URI parameters as its sole argument. If it returns null, the Router will respond with 404. Otherwise, the return value will be used as the Context. This method can accept more than one parameter. Any additional parameters will be treated as a dependency and resolved using Dependency Injection container.

[UriToContext] and IPageMiddleware<T> features are connected, but independent. You can use them both or just one them.

Middleware

Sometimes you want to define a behavior that will be applied to all the requests processed by the Router. To achieve this, implement Starcounter.Startup.Routing.IPageMiddleware interface and register it in the DI container.

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter;usingStarcounter.Startup.Routing;publicclassLoggingMiddleware:IPageMiddleware{privatereadonlyILogger<LoggingMiddleware>_logger;publicLoggingMiddleware(ILogger<LoggingMiddleware>logger){_logger=logger;}publicResponseRun(RoutingInforoutingInfo,Func<Response>next){_logger.LogInformation("Processing request");returnnext();}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();services.AddTransient<IPageMiddleware,LoggingMiddleware>();}

The above snippet will register LoggingMiddleware to run at every request processed by the Router.

AddRouter extension method mentioned before adds two pieces of middleware by default: MasterPageMiddleware and ContextMiddleware. If you want to prevent that behavior you can do that by passing false to includeDefaultMiddleware parameter:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter(false);// you can add them manually:// services.AddTransient<IPageMiddleware, MasterPageMiddleware>();// services.AddTransient<IPageMiddleware, ContextMiddleware>();}

Blending, Db.Scope, MasterPage

By default, every view-model you register with [Url] is both page URI and partial URI registered. When you access its page URI, Router creates a Db.Scope and retrieves its blending URI. This means, that if you request your view-model in the browser, the response can contain other, blended view-models as well. Router makes sure that they all share a common transaction.

A common application feature is to have some layout that wraps every response of an app and adds navigation features. This wrapping page would be called a master page. To enable it, create a view-model deriving from MasterPageBase and register it using SetMasterPage<T>:

{
"Html": "/MyApplication/views/MasterNavigation.html",
"InnerJson": {}
}
usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}}
<template><dom-bind><templateis="dom-bind"><h1>Application-wide header</h1><ahref="/MyApplication/Home">Go home</a><starcounter-includeview-model="{{model.InnerJson}}"></starcounter-include></template></dom-bind></template>
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage<MasterNavigationPage>();}

Controlling transaction scopes in master page

Without master page, all the blended view-models share a common transaction. With a master page like one defined above, all the blended view-models share a common transaction, but the master page itself has no transaction. You can change that if you want.

To put the master page in a transaction, you have to create it in Db.Scope. To do it, register your custom master page factory:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage((provider,routingInfo)=>Db.Scope(()=>newMasterNavigationPage()))}

The code above will create MasterNavigationPage in a transaction scope, but it will not be shared with the blended view-models. If you want it to be in shared transaction, you can change it by overriding ExecuteInScope in your master page:

usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}publicoverrideTExecuteInScope(Func<T>innerJsonFactory){returnAttachedScope.Scope(innerJsonFactory);}}

The code above will attach the blended view-models to the scope of the master page. That way all the view-models will share a transaction.

Dependency injection in view-models

To use services from the DI container in your view-model, declare a constructor that accepts dependencies as arguments. For more information about Dependency Injection, consult microsoft docs on DI.

publicpartialclassDogViewModel:Json{publicDogViewModel(IDogServicedogService){_dogService=dogService;}}

For a long time, Starcounter didn't support constructor injection, and used IInitPageWithDependncies marker interface instead. You would implement it and create public, non-static, void Init method that accepted your dependencies as parameters. Below is an example of that practice. It can be now safely converted to constructor injection.

// using Starcounter.Startup.Routing.Activation;// LEGACY CODEpublicpartialclassDogViewModel:Json,IInitPageWithDependencies{publicvoidInit(IDogServicedogService){_dogService=dogService;}}

⚠️Only view-models created by the Router (those i.e. created automatically by accessing a URI) will have their dependncies filled. View-models nested inside other view-model, that are created by Starcounter, will not automatically be created with dependencies.

// WON'T WORK// AllDogsViewModel.json{"Children":[{}]}// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){Children.Data=dogService.GetAllDogs();}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{// this won't even compilepublicChildViewModel(IDogServicedogService){// ...}}}

To fill dependencies for a nested view-model you have to create it by hand:

// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){foreach(vardogindogService.GetAllDogs()){Children.Add().Init(dogService);}}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{publicvoidInit(IDogServicedogService){// ...}}}

Services registered by default

DefaultStarcounterBootstrapper registers two aspnet.core's features by default - logging and options. You can read about them on docs.microsoft.com. All logs are printed on Standard Output by default.

UriHelper

UriHelper is a collection of static methods which ease working with Starcounter URIs. It exposes following methods. Each method below is accompanied by an example with output.

publicstaticstringPartialToPage(stringpartialUri)

Converts partial URI to page URI. E.g. PartialToPage("/MyApp/partial/dog") will return "/MyApp/dog".

publicstaticstringPageToPartial(stringpageUri)

Converts page URI to partial URI. E.g. PageToPartial("/MyApp/dog") will return "/MyApp/partial/dog".

publicstaticboolIsPartialUri(stringuri)

Returns true if the supplied URI is a partial URI. E.g. IsPartialUri("/MyApp/partial/dog") will return true, but IsPartialUri("/MyApp/dog") will return false.

publicstaticstringWithArguments(stringuriTemplate,paramsstring[]arguments)

Returns the supplied URI with its arguments filled. E.g. WithArguments("/MyApp/partial/dog/{?}", "xyz") will return "/MyApp/partial/dog/xyz".

Startup Filters

Usually when you want some code to execute during the startup of the application, you just put it in Configure method of your startup class. However, there's a second way to achieve it: define a class implementing IStartupFilter interface and register it in ConfigureServices. Below is a sample startup filter and a snippet to register it:

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter.Startup.Abstractions;namespaceStarcounter.Authorization.Authentication{publicclassLoggingStartupFilter:IStartupFilter{privatereadonlyILogger<LoggingStartupFilter>_logger;publicLoggingStartupFilter(ILogger<LoggingStartupFilter>logger){_logger=logger;}publicAction<IApplicationBuilder>Configure(Action<IApplicationBuilder>next){return app =>{_logger.LogInformation("Application started");next(app);};}}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;publicvoidConfigureServices(IServiceCollectionservices){services.AddTransient<IStartupFilter,LoggingStartupFilter>();}

This feature is especially useful in libraries, which do not directly control application's startup.

About

Bootstrapping library for 2.4 applications

Resources

Stars

0 stars

Watchers

18 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Starcounter.Startup

Dependency injection for Starcounter 2.4

Table of contents

Table of contents generated with markdown-toc

Installation

This package is available on nuget. You can get it there. To install with CLI run:

Install-Package Starcounter.Startup

Getting started

Create a startup class (commonly called Startup). It has to implement Starcounter.Startup.Abstractions.IStartup.

usingMicrosoft.Extensions.DependencyInjection;usingStarcounter.Startup.Abstractions;publicclassStartup:IStartup{publicvoidConfigureServices(IServiceCollectionservices){// here you can configure your DI container}publicvoidConfigure(IApplicationBuilderapplicationBuilder){// here you perform any start-up tasks for your application// applicationBuilder can be used to access the IServiceProvider}}

IStartup mimics asp.net core's startup concept. You can read about it on docs.microsoft.com In your Program.cs you should have only a single call to the bootstrapper:

usingStarcounter.Startup;publicclassProgram{publicstaticvoidMain(){DefaultStarcounterBootstrapper.Start(newStartup());}}

This will start your application according to the Startup class.

Router

Router is a class that helps creating the view-models and register them with URIs. To use it you have to add it to the DI container in ConfigureServices:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();}

Registering view-models with UrlAttribute

You can annotate your view-model with Starcounter.Startup.Routing.UrlAttribute:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs")][Url("/DogsApp/AllDogs")]// you can use UrlAttribute multiple timespublicpartialclassDogViewModel:Json{// ...}

⚠️ Be careful to use UrlAttribute from Starcounter.Startup.Routing namespace. Common mistake is to use one from System.Runtime.Remoting.Activation instead.

You have to tell the Router to scan your assembly for all view-models to register them:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().RegisterAllFromCurrentAssembly();// see also RegisterAllFromAssembly() if you keep your view-models in a separate project}

The snippets above will register a handler under "/DogsApp/Dogs" and "/DogsApp/AllDogs". This handler will return DogViewModel.

Exposing view-models to blending and browser

By default, applying [UrlAttribute] will expose your view-model to the browser (or anyone who uses HTTP) under the supplied URI, and to the Blending Engine under the partial URI.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs/{?}")]publicpartialclassDogViewModel:Json{// ...}

The code above will expose your view-model under /DogsApp/Dogs/{?} to the browser, and /DogsApp/partial/Dogs/{?} to the Blending Engine. You won't be able to call the second URI from the browser.

You can also expose your view-model to Blending or browser only.

// blending only[Url("/DogsApp/Dogs/{?}",External=false)]// browser only[Url("/DogsApp/Dogs/{?}",Blendable=false)]

Registering view-models manually

Sometimes you want to have more control over the registration of your handlers. You can achieve that with manual registration:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().HandleGet<DogViewModel>("/DogsApp/very-custom-uri/Dogs",newHandlerOptions{SelfOnly=true});// see also other overloads of HandleGet}

The snippet above will register a handler under "/DogsApp/very-custom-uri/Dogs". This handler will return DogViewModel, but will only be available to Blending engine.

Handling URI parameters, working with Context

In most cases when you use URI parameters, they correspond to a database entity of type T and your view-model implements IBound<T>. This case, illustrated below, is handled automatically:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{// ...}

In the example below, if you open URI /DogsApp/Dog/123, then:

  • Router (more specifically, ContextMiddleware) will look for a Dog with id 123 in the database
  • If it's not found, then 404 response will be returned
  • If it's found, then DogViewModel will be initialized, and then its Data property will be populated with the Dog found in the database

This is usually the desired behavior and if it satisfies you, can skip the rest of this chapter.

To understand how this works you first need to know what Context is. Context is the data represented by URI arguments. If the URI has a Dog id a parameter 123 (/DogsApp/Dog/123), then Context is the Dog entity with id 123.

If the view-model implements IBound<T>, then the type of the Context is inferred to be T. You can, however override this by implementing IPageContext<T> interface. Consider following example:

You want the following view-model to be accessible by ID of the Dog. i.e. accessing /DogsApp/Dog/123/Owner should initialize DogOwnerViewModel.Data to the owner of the Dog with id 123.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}/Owner")]publicpartialclassDogOwnerViewModel:Json,IBound<Person>,IPageContext<Dog>{publicvoidHandleContext(Dogcontext){this.Data=context.Owner;}// ...}

Here, the context type would've been inferred to Person, but we explicitly declared it to be Dog. To implement the interface we also had to specify what happens with the context. If you don't implement this interface, but implement IBound<T>, the context is simply assigned to Data property.

But how is this context object fetched? By default, if the URI has only one parameter and Context is a database entity, the parameter value is used to fetch the Context from the database. However, if one of those conditions are not met or you want to override the default behavior, you must use [UriToContext]:

// using Starcounter.Startup.Routing;// using Starcounter.Linq;[Url("/DogsApp/DogByName/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{[UriToContext]// the name of this method is irrelevant, but calling it UriToContext is a good practicepublicstaticDogUriToContext(string[]args,IDogsRepositorydogsRepository){// args is guaranteed to have one element, because its only URI has only one parameter// returning null will cause the Router to respond with 404returndogsRepository.GetByName(args[0]);}// ...}

In this example instead of fetching the Context by its ID, we fetch it using its Name property. To use [UriToContext], apply it to one method that:

  • is public static
  • has return type assignable to Context type
  • has the first parameter of type string[]

This method will be invoked before the view-model is created. It will be passed URI parameters as its sole argument. If it returns null, the Router will respond with 404. Otherwise, the return value will be used as the Context. This method can accept more than one parameter. Any additional parameters will be treated as a dependency and resolved using Dependency Injection container.

[UriToContext] and IPageMiddleware<T> features are connected, but independent. You can use them both or just one them.

Middleware

Sometimes you want to define a behavior that will be applied to all the requests processed by the Router. To achieve this, implement Starcounter.Startup.Routing.IPageMiddleware interface and register it in the DI container.

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter;usingStarcounter.Startup.Routing;publicclassLoggingMiddleware:IPageMiddleware{privatereadonlyILogger<LoggingMiddleware>_logger;publicLoggingMiddleware(ILogger<LoggingMiddleware>logger){_logger=logger;}publicResponseRun(RoutingInforoutingInfo,Func<Response>next){_logger.LogInformation("Processing request");returnnext();}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();services.AddTransient<IPageMiddleware,LoggingMiddleware>();}

The above snippet will register LoggingMiddleware to run at every request processed by the Router.

AddRouter extension method mentioned before adds two pieces of middleware by default: MasterPageMiddleware and ContextMiddleware. If you want to prevent that behavior you can do that by passing false to includeDefaultMiddleware parameter:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter(false);// you can add them manually:// services.AddTransient<IPageMiddleware, MasterPageMiddleware>();// services.AddTransient<IPageMiddleware, ContextMiddleware>();}

Blending, Db.Scope, MasterPage

By default, every view-model you register with [Url] is both page URI and partial URI registered. When you access its page URI, Router creates a Db.Scope and retrieves its blending URI. This means, that if you request your view-model in the browser, the response can contain other, blended view-models as well. Router makes sure that they all share a common transaction.

A common application feature is to have some layout that wraps every response of an app and adds navigation features. This wrapping page would be called a master page. To enable it, create a view-model deriving from MasterPageBase and register it using SetMasterPage<T>:

{
"Html": "/MyApplication/views/MasterNavigation.html",
"InnerJson": {}
}
usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}}
<template><dom-bind><templateis="dom-bind"><h1>Application-wide header</h1><ahref="/MyApplication/Home">Go home</a><starcounter-includeview-model="{{model.InnerJson}}"></starcounter-include></template></dom-bind></template>
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage<MasterNavigationPage>();}

Controlling transaction scopes in master page

Without master page, all the blended view-models share a common transaction. With a master page like one defined above, all the blended view-models share a common transaction, but the master page itself has no transaction. You can change that if you want.

To put the master page in a transaction, you have to create it in Db.Scope. To do it, register your custom master page factory:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage((provider,routingInfo)=>Db.Scope(()=>newMasterNavigationPage()))}

The code above will create MasterNavigationPage in a transaction scope, but it will not be shared with the blended view-models. If you want it to be in shared transaction, you can change it by overriding ExecuteInScope in your master page:

usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}publicoverrideTExecuteInScope(Func<T>innerJsonFactory){returnAttachedScope.Scope(innerJsonFactory);}}

The code above will attach the blended view-models to the scope of the master page. That way all the view-models will share a transaction.

Dependency injection in view-models

To use services from the DI container in your view-model, declare a constructor that accepts dependencies as arguments. For more information about Dependency Injection, consult microsoft docs on DI.

publicpartialclassDogViewModel:Json{publicDogViewModel(IDogServicedogService){_dogService=dogService;}}

For a long time, Starcounter didn't support constructor injection, and used IInitPageWithDependncies marker interface instead. You would implement it and create public, non-static, void Init method that accepted your dependencies as parameters. Below is an example of that practice. It can be now safely converted to constructor injection.

// using Starcounter.Startup.Routing.Activation;// LEGACY CODEpublicpartialclassDogViewModel:Json,IInitPageWithDependencies{publicvoidInit(IDogServicedogService){_dogService=dogService;}}

⚠️Only view-models created by the Router (those i.e. created automatically by accessing a URI) will have their dependncies filled. View-models nested inside other view-model, that are created by Starcounter, will not automatically be created with dependencies.

// WON'T WORK// AllDogsViewModel.json{"Children":[{}]}// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){Children.Data=dogService.GetAllDogs();}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{// this won't even compilepublicChildViewModel(IDogServicedogService){// ...}}}

To fill dependencies for a nested view-model you have to create it by hand:

// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){foreach(vardogindogService.GetAllDogs()){Children.Add().Init(dogService);}}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{publicvoidInit(IDogServicedogService){// ...}}}

Services registered by default

DefaultStarcounterBootstrapper registers two aspnet.core's features by default - logging and options. You can read about them on docs.microsoft.com. All logs are printed on Standard Output by default.

UriHelper

UriHelper is a collection of static methods which ease working with Starcounter URIs. It exposes following methods. Each method below is accompanied by an example with output.

publicstaticstringPartialToPage(stringpartialUri)

Converts partial URI to page URI. E.g. PartialToPage("/MyApp/partial/dog") will return "/MyApp/dog".

publicstaticstringPageToPartial(stringpageUri)

Converts page URI to partial URI. E.g. PageToPartial("/MyApp/dog") will return "/MyApp/partial/dog".

publicstaticboolIsPartialUri(stringuri)

Returns true if the supplied URI is a partial URI. E.g. IsPartialUri("/MyApp/partial/dog") will return true, but IsPartialUri("/MyApp/dog") will return false.

publicstaticstringWithArguments(stringuriTemplate,paramsstring[]arguments)

Returns the supplied URI with its arguments filled. E.g. WithArguments("/MyApp/partial/dog/{?}", "xyz") will return "/MyApp/partial/dog/xyz".

Startup Filters

Usually when you want some code to execute during the startup of the application, you just put it in Configure method of your startup class. However, there's a second way to achieve it: define a class implementing IStartupFilter interface and register it in ConfigureServices. Below is a sample startup filter and a snippet to register it:

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter.Startup.Abstractions;namespaceStarcounter.Authorization.Authentication{publicclassLoggingStartupFilter:IStartupFilter{privatereadonlyILogger<LoggingStartupFilter>_logger;publicLoggingStartupFilter(ILogger<LoggingStartupFilter>logger){_logger=logger;}publicAction<IApplicationBuilder>Configure(Action<IApplicationBuilder>next){return app =>{_logger.LogInformation("Application started");next(app);};}}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;publicvoidConfigureServices(IServiceCollectionservices){services.AddTransient<IStartupFilter,LoggingStartupFilter>();}

This feature is especially useful in libraries, which do not directly control application's startup.

About

Bootstrapping library for 2.4 applications

Resources

Stars

0 stars

Watchers

18 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Starcounter.Startup

Dependency injection for Starcounter 2.4

Table of contents

Table of contents generated with markdown-toc

Installation

This package is available on nuget. You can get it there. To install with CLI run:

Install-Package Starcounter.Startup

Getting started

Create a startup class (commonly called Startup). It has to implement Starcounter.Startup.Abstractions.IStartup.

usingMicrosoft.Extensions.DependencyInjection;usingStarcounter.Startup.Abstractions;publicclassStartup:IStartup{publicvoidConfigureServices(IServiceCollectionservices){// here you can configure your DI container}publicvoidConfigure(IApplicationBuilderapplicationBuilder){// here you perform any start-up tasks for your application// applicationBuilder can be used to access the IServiceProvider}}

IStartup mimics asp.net core's startup concept. You can read about it on docs.microsoft.com In your Program.cs you should have only a single call to the bootstrapper:

usingStarcounter.Startup;publicclassProgram{publicstaticvoidMain(){DefaultStarcounterBootstrapper.Start(newStartup());}}

This will start your application according to the Startup class.

Router

Router is a class that helps creating the view-models and register them with URIs. To use it you have to add it to the DI container in ConfigureServices:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();}

Registering view-models with UrlAttribute

You can annotate your view-model with Starcounter.Startup.Routing.UrlAttribute:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs")][Url("/DogsApp/AllDogs")]// you can use UrlAttribute multiple timespublicpartialclassDogViewModel:Json{// ...}

⚠️ Be careful to use UrlAttribute from Starcounter.Startup.Routing namespace. Common mistake is to use one from System.Runtime.Remoting.Activation instead.

You have to tell the Router to scan your assembly for all view-models to register them:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().RegisterAllFromCurrentAssembly();// see also RegisterAllFromAssembly() if you keep your view-models in a separate project}

The snippets above will register a handler under "/DogsApp/Dogs" and "/DogsApp/AllDogs". This handler will return DogViewModel.

Exposing view-models to blending and browser

By default, applying [UrlAttribute] will expose your view-model to the browser (or anyone who uses HTTP) under the supplied URI, and to the Blending Engine under the partial URI.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs/{?}")]publicpartialclassDogViewModel:Json{// ...}

The code above will expose your view-model under /DogsApp/Dogs/{?} to the browser, and /DogsApp/partial/Dogs/{?} to the Blending Engine. You won't be able to call the second URI from the browser.

You can also expose your view-model to Blending or browser only.

// blending only[Url("/DogsApp/Dogs/{?}",External=false)]// browser only[Url("/DogsApp/Dogs/{?}",Blendable=false)]

Registering view-models manually

Sometimes you want to have more control over the registration of your handlers. You can achieve that with manual registration:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().HandleGet<DogViewModel>("/DogsApp/very-custom-uri/Dogs",newHandlerOptions{SelfOnly=true});// see also other overloads of HandleGet}

The snippet above will register a handler under "/DogsApp/very-custom-uri/Dogs". This handler will return DogViewModel, but will only be available to Blending engine.

Handling URI parameters, working with Context

In most cases when you use URI parameters, they correspond to a database entity of type T and your view-model implements IBound<T>. This case, illustrated below, is handled automatically:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{// ...}

In the example below, if you open URI /DogsApp/Dog/123, then:

  • Router (more specifically, ContextMiddleware) will look for a Dog with id 123 in the database
  • If it's not found, then 404 response will be returned
  • If it's found, then DogViewModel will be initialized, and then its Data property will be populated with the Dog found in the database

This is usually the desired behavior and if it satisfies you, can skip the rest of this chapter.

To understand how this works you first need to know what Context is. Context is the data represented by URI arguments. If the URI has a Dog id a parameter 123 (/DogsApp/Dog/123), then Context is the Dog entity with id 123.

If the view-model implements IBound<T>, then the type of the Context is inferred to be T. You can, however override this by implementing IPageContext<T> interface. Consider following example:

You want the following view-model to be accessible by ID of the Dog. i.e. accessing /DogsApp/Dog/123/Owner should initialize DogOwnerViewModel.Data to the owner of the Dog with id 123.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}/Owner")]publicpartialclassDogOwnerViewModel:Json,IBound<Person>,IPageContext<Dog>{publicvoidHandleContext(Dogcontext){this.Data=context.Owner;}// ...}

Here, the context type would've been inferred to Person, but we explicitly declared it to be Dog. To implement the interface we also had to specify what happens with the context. If you don't implement this interface, but implement IBound<T>, the context is simply assigned to Data property.

But how is this context object fetched? By default, if the URI has only one parameter and Context is a database entity, the parameter value is used to fetch the Context from the database. However, if one of those conditions are not met or you want to override the default behavior, you must use [UriToContext]:

// using Starcounter.Startup.Routing;// using Starcounter.Linq;[Url("/DogsApp/DogByName/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{[UriToContext]// the name of this method is irrelevant, but calling it UriToContext is a good practicepublicstaticDogUriToContext(string[]args,IDogsRepositorydogsRepository){// args is guaranteed to have one element, because its only URI has only one parameter// returning null will cause the Router to respond with 404returndogsRepository.GetByName(args[0]);}// ...}

In this example instead of fetching the Context by its ID, we fetch it using its Name property. To use [UriToContext], apply it to one method that:

  • is public static
  • has return type assignable to Context type
  • has the first parameter of type string[]

This method will be invoked before the view-model is created. It will be passed URI parameters as its sole argument. If it returns null, the Router will respond with 404. Otherwise, the return value will be used as the Context. This method can accept more than one parameter. Any additional parameters will be treated as a dependency and resolved using Dependency Injection container.

[UriToContext] and IPageMiddleware<T> features are connected, but independent. You can use them both or just one them.

Middleware

Sometimes you want to define a behavior that will be applied to all the requests processed by the Router. To achieve this, implement Starcounter.Startup.Routing.IPageMiddleware interface and register it in the DI container.

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter;usingStarcounter.Startup.Routing;publicclassLoggingMiddleware:IPageMiddleware{privatereadonlyILogger<LoggingMiddleware>_logger;publicLoggingMiddleware(ILogger<LoggingMiddleware>logger){_logger=logger;}publicResponseRun(RoutingInforoutingInfo,Func<Response>next){_logger.LogInformation("Processing request");returnnext();}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();services.AddTransient<IPageMiddleware,LoggingMiddleware>();}

The above snippet will register LoggingMiddleware to run at every request processed by the Router.

AddRouter extension method mentioned before adds two pieces of middleware by default: MasterPageMiddleware and ContextMiddleware. If you want to prevent that behavior you can do that by passing false to includeDefaultMiddleware parameter:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter(false);// you can add them manually:// services.AddTransient<IPageMiddleware, MasterPageMiddleware>();// services.AddTransient<IPageMiddleware, ContextMiddleware>();}

Blending, Db.Scope, MasterPage

By default, every view-model you register with [Url] is both page URI and partial URI registered. When you access its page URI, Router creates a Db.Scope and retrieves its blending URI. This means, that if you request your view-model in the browser, the response can contain other, blended view-models as well. Router makes sure that they all share a common transaction.

A common application feature is to have some layout that wraps every response of an app and adds navigation features. This wrapping page would be called a master page. To enable it, create a view-model deriving from MasterPageBase and register it using SetMasterPage<T>:

{
"Html": "/MyApplication/views/MasterNavigation.html",
"InnerJson": {}
}
usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}}
<template><dom-bind><templateis="dom-bind"><h1>Application-wide header</h1><ahref="/MyApplication/Home">Go home</a><starcounter-includeview-model="{{model.InnerJson}}"></starcounter-include></template></dom-bind></template>
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage<MasterNavigationPage>();}

Controlling transaction scopes in master page

Without master page, all the blended view-models share a common transaction. With a master page like one defined above, all the blended view-models share a common transaction, but the master page itself has no transaction. You can change that if you want.

To put the master page in a transaction, you have to create it in Db.Scope. To do it, register your custom master page factory:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage((provider,routingInfo)=>Db.Scope(()=>newMasterNavigationPage()))}

The code above will create MasterNavigationPage in a transaction scope, but it will not be shared with the blended view-models. If you want it to be in shared transaction, you can change it by overriding ExecuteInScope in your master page:

usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}publicoverrideTExecuteInScope(Func<T>innerJsonFactory){returnAttachedScope.Scope(innerJsonFactory);}}

The code above will attach the blended view-models to the scope of the master page. That way all the view-models will share a transaction.

Dependency injection in view-models

To use services from the DI container in your view-model, declare a constructor that accepts dependencies as arguments. For more information about Dependency Injection, consult microsoft docs on DI.

publicpartialclassDogViewModel:Json{publicDogViewModel(IDogServicedogService){_dogService=dogService;}}

For a long time, Starcounter didn't support constructor injection, and used IInitPageWithDependncies marker interface instead. You would implement it and create public, non-static, void Init method that accepted your dependencies as parameters. Below is an example of that practice. It can be now safely converted to constructor injection.

// using Starcounter.Startup.Routing.Activation;// LEGACY CODEpublicpartialclassDogViewModel:Json,IInitPageWithDependencies{publicvoidInit(IDogServicedogService){_dogService=dogService;}}

⚠️Only view-models created by the Router (those i.e. created automatically by accessing a URI) will have their dependncies filled. View-models nested inside other view-model, that are created by Starcounter, will not automatically be created with dependencies.

// WON'T WORK// AllDogsViewModel.json{"Children":[{}]}// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){Children.Data=dogService.GetAllDogs();}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{// this won't even compilepublicChildViewModel(IDogServicedogService){// ...}}}

To fill dependencies for a nested view-model you have to create it by hand:

// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){foreach(vardogindogService.GetAllDogs()){Children.Add().Init(dogService);}}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{publicvoidInit(IDogServicedogService){// ...}}}

Services registered by default

DefaultStarcounterBootstrapper registers two aspnet.core's features by default - logging and options. You can read about them on docs.microsoft.com. All logs are printed on Standard Output by default.

UriHelper

UriHelper is a collection of static methods which ease working with Starcounter URIs. It exposes following methods. Each method below is accompanied by an example with output.

publicstaticstringPartialToPage(stringpartialUri)

Converts partial URI to page URI. E.g. PartialToPage("/MyApp/partial/dog") will return "/MyApp/dog".

publicstaticstringPageToPartial(stringpageUri)

Converts page URI to partial URI. E.g. PageToPartial("/MyApp/dog") will return "/MyApp/partial/dog".

publicstaticboolIsPartialUri(stringuri)

Returns true if the supplied URI is a partial URI. E.g. IsPartialUri("/MyApp/partial/dog") will return true, but IsPartialUri("/MyApp/dog") will return false.

publicstaticstringWithArguments(stringuriTemplate,paramsstring[]arguments)

Returns the supplied URI with its arguments filled. E.g. WithArguments("/MyApp/partial/dog/{?}", "xyz") will return "/MyApp/partial/dog/xyz".

Startup Filters

Usually when you want some code to execute during the startup of the application, you just put it in Configure method of your startup class. However, there's a second way to achieve it: define a class implementing IStartupFilter interface and register it in ConfigureServices. Below is a sample startup filter and a snippet to register it:

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter.Startup.Abstractions;namespaceStarcounter.Authorization.Authentication{publicclassLoggingStartupFilter:IStartupFilter{privatereadonlyILogger<LoggingStartupFilter>_logger;publicLoggingStartupFilter(ILogger<LoggingStartupFilter>logger){_logger=logger;}publicAction<IApplicationBuilder>Configure(Action<IApplicationBuilder>next){return app =>{_logger.LogInformation("Application started");next(app);};}}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;publicvoidConfigureServices(IServiceCollectionservices){services.AddTransient<IStartupFilter,LoggingStartupFilter>();}

This feature is especially useful in libraries, which do not directly control application's startup.

About

Bootstrapping library for 2.4 applications

Resources

Stars

0 stars

Watchers

18 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Starcounter.Startup

Dependency injection for Starcounter 2.4

Table of contents

Table of contents generated with markdown-toc

Installation

This package is available on nuget. You can get it there. To install with CLI run:

Install-Package Starcounter.Startup

Getting started

Create a startup class (commonly called Startup). It has to implement Starcounter.Startup.Abstractions.IStartup.

usingMicrosoft.Extensions.DependencyInjection;usingStarcounter.Startup.Abstractions;publicclassStartup:IStartup{publicvoidConfigureServices(IServiceCollectionservices){// here you can configure your DI container}publicvoidConfigure(IApplicationBuilderapplicationBuilder){// here you perform any start-up tasks for your application// applicationBuilder can be used to access the IServiceProvider}}

IStartup mimics asp.net core's startup concept. You can read about it on docs.microsoft.com In your Program.cs you should have only a single call to the bootstrapper:

usingStarcounter.Startup;publicclassProgram{publicstaticvoidMain(){DefaultStarcounterBootstrapper.Start(newStartup());}}

This will start your application according to the Startup class.

Router

Router is a class that helps creating the view-models and register them with URIs. To use it you have to add it to the DI container in ConfigureServices:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();}

Registering view-models with UrlAttribute

You can annotate your view-model with Starcounter.Startup.Routing.UrlAttribute:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs")][Url("/DogsApp/AllDogs")]// you can use UrlAttribute multiple timespublicpartialclassDogViewModel:Json{// ...}

⚠️ Be careful to use UrlAttribute from Starcounter.Startup.Routing namespace. Common mistake is to use one from System.Runtime.Remoting.Activation instead.

You have to tell the Router to scan your assembly for all view-models to register them:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().RegisterAllFromCurrentAssembly();// see also RegisterAllFromAssembly() if you keep your view-models in a separate project}

The snippets above will register a handler under "/DogsApp/Dogs" and "/DogsApp/AllDogs". This handler will return DogViewModel.

Exposing view-models to blending and browser

By default, applying [UrlAttribute] will expose your view-model to the browser (or anyone who uses HTTP) under the supplied URI, and to the Blending Engine under the partial URI.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dogs/{?}")]publicpartialclassDogViewModel:Json{// ...}

The code above will expose your view-model under /DogsApp/Dogs/{?} to the browser, and /DogsApp/partial/Dogs/{?} to the Blending Engine. You won't be able to call the second URI from the browser.

You can also expose your view-model to Blending or browser only.

// blending only[Url("/DogsApp/Dogs/{?}",External=false)]// browser only[Url("/DogsApp/Dogs/{?}",Blendable=false)]

Registering view-models manually

Sometimes you want to have more control over the registration of your handlers. You can achieve that with manual registration:

// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigure(IApplicationBuilderapplicationBuilder){applicationBuilder.ApplicationServices.GetRouter().HandleGet<DogViewModel>("/DogsApp/very-custom-uri/Dogs",newHandlerOptions{SelfOnly=true});// see also other overloads of HandleGet}

The snippet above will register a handler under "/DogsApp/very-custom-uri/Dogs". This handler will return DogViewModel, but will only be available to Blending engine.

Handling URI parameters, working with Context

In most cases when you use URI parameters, they correspond to a database entity of type T and your view-model implements IBound<T>. This case, illustrated below, is handled automatically:

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{// ...}

In the example below, if you open URI /DogsApp/Dog/123, then:

  • Router (more specifically, ContextMiddleware) will look for a Dog with id 123 in the database
  • If it's not found, then 404 response will be returned
  • If it's found, then DogViewModel will be initialized, and then its Data property will be populated with the Dog found in the database

This is usually the desired behavior and if it satisfies you, can skip the rest of this chapter.

To understand how this works you first need to know what Context is. Context is the data represented by URI arguments. If the URI has a Dog id a parameter 123 (/DogsApp/Dog/123), then Context is the Dog entity with id 123.

If the view-model implements IBound<T>, then the type of the Context is inferred to be T. You can, however override this by implementing IPageContext<T> interface. Consider following example:

You want the following view-model to be accessible by ID of the Dog. i.e. accessing /DogsApp/Dog/123/Owner should initialize DogOwnerViewModel.Data to the owner of the Dog with id 123.

// using Starcounter.Startup.Routing;[Url("/DogsApp/Dog/{?}/Owner")]publicpartialclassDogOwnerViewModel:Json,IBound<Person>,IPageContext<Dog>{publicvoidHandleContext(Dogcontext){this.Data=context.Owner;}// ...}

Here, the context type would've been inferred to Person, but we explicitly declared it to be Dog. To implement the interface we also had to specify what happens with the context. If you don't implement this interface, but implement IBound<T>, the context is simply assigned to Data property.

But how is this context object fetched? By default, if the URI has only one parameter and Context is a database entity, the parameter value is used to fetch the Context from the database. However, if one of those conditions are not met or you want to override the default behavior, you must use [UriToContext]:

// using Starcounter.Startup.Routing;// using Starcounter.Linq;[Url("/DogsApp/DogByName/{?}")]publicpartialclassDogViewModel:Json,IBound<Dog>{[UriToContext]// the name of this method is irrelevant, but calling it UriToContext is a good practicepublicstaticDogUriToContext(string[]args,IDogsRepositorydogsRepository){// args is guaranteed to have one element, because its only URI has only one parameter// returning null will cause the Router to respond with 404returndogsRepository.GetByName(args[0]);}// ...}

In this example instead of fetching the Context by its ID, we fetch it using its Name property. To use [UriToContext], apply it to one method that:

  • is public static
  • has return type assignable to Context type
  • has the first parameter of type string[]

This method will be invoked before the view-model is created. It will be passed URI parameters as its sole argument. If it returns null, the Router will respond with 404. Otherwise, the return value will be used as the Context. This method can accept more than one parameter. Any additional parameters will be treated as a dependency and resolved using Dependency Injection container.

[UriToContext] and IPageMiddleware<T> features are connected, but independent. You can use them both or just one them.

Middleware

Sometimes you want to define a behavior that will be applied to all the requests processed by the Router. To achieve this, implement Starcounter.Startup.Routing.IPageMiddleware interface and register it in the DI container.

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter;usingStarcounter.Startup.Routing;publicclassLoggingMiddleware:IPageMiddleware{privatereadonlyILogger<LoggingMiddleware>_logger;publicLoggingMiddleware(ILogger<LoggingMiddleware>logger){_logger=logger;}publicResponseRun(RoutingInforoutingInfo,Func<Response>next){_logger.LogInformation("Processing request");returnnext();}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter();services.AddTransient<IPageMiddleware,LoggingMiddleware>();}

The above snippet will register LoggingMiddleware to run at every request processed by the Router.

AddRouter extension method mentioned before adds two pieces of middleware by default: MasterPageMiddleware and ContextMiddleware. If you want to prevent that behavior you can do that by passing false to includeDefaultMiddleware parameter:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter(false);// you can add them manually:// services.AddTransient<IPageMiddleware, MasterPageMiddleware>();// services.AddTransient<IPageMiddleware, ContextMiddleware>();}

Blending, Db.Scope, MasterPage

By default, every view-model you register with [Url] is both page URI and partial URI registered. When you access its page URI, Router creates a Db.Scope and retrieves its blending URI. This means, that if you request your view-model in the browser, the response can contain other, blended view-models as well. Router makes sure that they all share a common transaction.

A common application feature is to have some layout that wraps every response of an app and adds navigation features. This wrapping page would be called a master page. To enable it, create a view-model deriving from MasterPageBase and register it using SetMasterPage<T>:

{
"Html": "/MyApplication/views/MasterNavigation.html",
"InnerJson": {}
}
usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}}
<template><dom-bind><templateis="dom-bind"><h1>Application-wide header</h1><ahref="/MyApplication/Home">Go home</a><starcounter-includeview-model="{{model.InnerJson}}"></starcounter-include></template></dom-bind></template>
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage<MasterNavigationPage>();}

Controlling transaction scopes in master page

Without master page, all the blended view-models share a common transaction. With a master page like one defined above, all the blended view-models share a common transaction, but the master page itself has no transaction. You can change that if you want.

To put the master page in a transaction, you have to create it in Db.Scope. To do it, register your custom master page factory:

// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;// using Starcounter.Startup.Routing;publicvoidConfigureServices(IServiceCollectionservices){services.AddRouter().SetMasterPage((provider,routingInfo)=>Db.Scope(()=>newMasterNavigationPage()))}

The code above will create MasterNavigationPage in a transaction scope, but it will not be shared with the blended view-models. If you want it to be in shared transaction, you can change it by overriding ExecuteInScope in your master page:

usingStarcounter;usingStarcounter.Startup.Routing.Middleware;publicpartialclassMasterNavigationPage:MasterPageBase{publicoverridevoidSetPartial(Jsonpartial){InnerJson=partial;}publicoverrideTExecuteInScope(Func<T>innerJsonFactory){returnAttachedScope.Scope(innerJsonFactory);}}

The code above will attach the blended view-models to the scope of the master page. That way all the view-models will share a transaction.

Dependency injection in view-models

To use services from the DI container in your view-model, declare a constructor that accepts dependencies as arguments. For more information about Dependency Injection, consult microsoft docs on DI.

publicpartialclassDogViewModel:Json{publicDogViewModel(IDogServicedogService){_dogService=dogService;}}

For a long time, Starcounter didn't support constructor injection, and used IInitPageWithDependncies marker interface instead. You would implement it and create public, non-static, void Init method that accepted your dependencies as parameters. Below is an example of that practice. It can be now safely converted to constructor injection.

// using Starcounter.Startup.Routing.Activation;// LEGACY CODEpublicpartialclassDogViewModel:Json,IInitPageWithDependencies{publicvoidInit(IDogServicedogService){_dogService=dogService;}}

⚠️Only view-models created by the Router (those i.e. created automatically by accessing a URI) will have their dependncies filled. View-models nested inside other view-model, that are created by Starcounter, will not automatically be created with dependencies.

// WON'T WORK// AllDogsViewModel.json{"Children":[{}]}// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){Children.Data=dogService.GetAllDogs();}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{// this won't even compilepublicChildViewModel(IDogServicedogService){// ...}}}

To fill dependencies for a nested view-model you have to create it by hand:

// AllDogsViewModel.json.cspublicpartialclassAllDogsViewModel:Json{publicDogViewModel(IDogServicedogService){foreach(vardogindogService.GetAllDogs()){Children.Add().Init(dogService);}}[AllDogsViewModel_json.Children]publicpartialclassChildViewModel:Json{publicvoidInit(IDogServicedogService){// ...}}}

Services registered by default

DefaultStarcounterBootstrapper registers two aspnet.core's features by default - logging and options. You can read about them on docs.microsoft.com. All logs are printed on Standard Output by default.

UriHelper

UriHelper is a collection of static methods which ease working with Starcounter URIs. It exposes following methods. Each method below is accompanied by an example with output.

publicstaticstringPartialToPage(stringpartialUri)

Converts partial URI to page URI. E.g. PartialToPage("/MyApp/partial/dog") will return "/MyApp/dog".

publicstaticstringPageToPartial(stringpageUri)

Converts page URI to partial URI. E.g. PageToPartial("/MyApp/dog") will return "/MyApp/partial/dog".

publicstaticboolIsPartialUri(stringuri)

Returns true if the supplied URI is a partial URI. E.g. IsPartialUri("/MyApp/partial/dog") will return true, but IsPartialUri("/MyApp/dog") will return false.

publicstaticstringWithArguments(stringuriTemplate,paramsstring[]arguments)

Returns the supplied URI with its arguments filled. E.g. WithArguments("/MyApp/partial/dog/{?}", "xyz") will return "/MyApp/partial/dog/xyz".

Startup Filters

Usually when you want some code to execute during the startup of the application, you just put it in Configure method of your startup class. However, there's a second way to achieve it: define a class implementing IStartupFilter interface and register it in ConfigureServices. Below is a sample startup filter and a snippet to register it:

usingSystem;usingMicrosoft.Extensions.Logging;usingStarcounter.Startup.Abstractions;namespaceStarcounter.Authorization.Authentication{publicclassLoggingStartupFilter:IStartupFilter{privatereadonlyILogger<LoggingStartupFilter>_logger;publicLoggingStartupFilter(ILogger<LoggingStartupFilter>logger){_logger=logger;}publicAction<IApplicationBuilder>Configure(Action<IApplicationBuilder>next){return app =>{_logger.LogInformation("Application started");next(app);};}}}
// using Microsoft.Extensions.DependencyInjection;// using Starcounter.Startup.Abstractions;publicvoidConfigureServices(IServiceCollectionservices){services.AddTransient<IStartupFilter,LoggingStartupFilter>();}

This feature is especially useful in libraries, which do not directly control application's startup.

About

Bootstrapping library for 2.4 applications

Resources

Stars

0 stars

Watchers

18 watching

Forks

Releases

Packages

Used by

Contributors

Languages