## Getting Set Up
Before we start writing some code, you're going to want to grab one of our pre-configured, getting-started packages. We've got two versions available to you: one uses ECMAScript 2016 (JavaScript vNext) and the other one uses TypeScript. Choose one now by clicking one of the buttons below.
Based on your selection, you'll want to configure this documentation to show the appropriate language for all samples. Look at the top of this page. You will see a language selector. Be sure to select the language that matches the package that you downloaded.
Now that you've downloaded a starter package, you need to unzip it on your hard drive. Doing so will provide you with the default folder structure, scripts and styles needed to complete this tutorial and continue your learning and experimentation afterward. With the folder structure in place, next we'll need to start a web server to serve up your index.html page, so we can view it in a browser. How you go about doing that depends on which server-side technology you want to use. Below are instructions for a couple of common scenarios:
* **Visual Studio** - Open Visual Studio 2015. Using the main menu, select File > Open > Web site... Choose the starter kit folder. Once the folder opens, right click on index.html and select "View in Browser". This will fire up IISExpress and serve index.html.
* **NodeJS** - To start up a simple web server in the app folder, first globally install the http-server command with `npm install http-server -g`. (In some environments you many need to use `sudo`). Once that is installed, change directory to the starter kit folder. You can now spin up the server from within the folder with the following command `http-server -o`.
* **Firefox** - If you don't want to worry about setting up a web server, Firefox is flexible enough to serve the app directly from your hard drive. Simply open the `index.html` file with Firefox.
Once you've got your web server set up, navigate to the `index.html` page using your favorite browser. If everyting is working correctly, you should see the message "Welcome to Aurelia!" displayed.
## The Index.html Page
If you've followed along this far, you now have everything set up to help you learn Aurelia. Let's start by taking a look at the `index.html` file. This file provides a good template for new Aurelia-based apps.
Aurelia
Yes, that's it. This is the only HTML page in our application. The head of the document is pretty straight forward: just a style sheet link and some metadata. It's the body that's interesting.
Let's start with the script tags. First we have _system.js_, our standards-based module loader. It's what loads the Aurelia library as well as your own code. Next we have _config.js_. This contains configuration for the loader. It's generated automatically whenever you install packages with our tools. We've gone ahead and pre-installed everything for you in this tutorial, so you don't need to worry about that yet.
Once we have our module loader and its configuration, we load the `aurelia-bootstrapper` module with a call to `System.import`.
When the bootstrapper loads it inspects the HTML document for _aurelia-app_ attributes. In this case it will find that the body has an `aurelia-app` attribute. This tells the bootstrapper to load our _app_ view-model and its view, conventionally located in _app.js_ and _app.html_ and then compose them as an Aurelia application in the DOM.
Wait a minute....we don't have an _app_ view-model or view. Ummm...WHAT NOW!?
## Creating Your First Screen
In Aurelia, user interface components have two parts: a _view_ and a _view-model_. The _view_ is written with HTML and is rendered into the DOM. The _view-model_ is written with JavaScript/TypeScript and provides data and behavior to the _view_. Aurelia's powerful _databinding_ links the two pieces together allowing changes in your data to be reflected in the _view_ and vice versa. This Separation of Concerns is great for developer/designer collaboration, maintainability, architectural flexibility and even source control.
Let's see how it works...
In the _src_ folder find the _app.html_ and _app.js_ files. This is the app component's view and view-model, the files that the bootstrapper is loading. Let's start by replacing the _view-model_ with a simple class to hold a _firstName_ and _lastName_. We'll also add a computed property for _fullName_ and a method to "submit" the person. Here's what your _app.js_ file should look like after you have made the changes:
export class Welcome {
constructor() {
this.heading = 'Welcome to Aurelia!';
this.firstName = 'John';
this.lastName = 'Doe';
}
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
submit() {
alert(`Welcome, ${this.fullName}!`);
}
}
export class Welcome {
heading = 'Welcome to Aurelia!';
firstName = 'John';
lastName = 'Doe';
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
submit() {
alert(`Welcome, ${this.fullName}!`);
}
}
export class Welcome {
heading: string = 'Welcome to Aurelia!';
firstName: string = 'John';
lastName: string = 'Doe';
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
submit(): void {
alert(`Welcome, ${this.fullName}!`);
}
}
Ok. Now that we have a _view-model_ with some basic data and behavior, let's use the following HTML to create the _view_:
${heading}
The first thing to notice is that all views are contained within a `template` tag, part of the W3C spec for Web Components. This particular view is a basic input form. Look at the input controls. Did you notice `value.bind="firstName"`? That databinds the input's _value_ to the _firstName_ property in our view-model. Any time the view-model's property changes, the input will be updated with the new value. Any time you change the value in the input control, Aurelia will push the new value into your view-model. It's that easy.
There are a couple more interesting things in this example. In the last form group you can see this syntax in the HTML content: `\${fullName}`. That's a string interpolation. It's a one-way binding from the view-model into the view that is automatically converted to a string and interpolated into the document. Finally, have a look at the form element itself. You should notice this: `submit.trigger="submit()"`. That's an event binding. Whenever the form's _submit_ event is fired the _submit_ method on the view-model will be invoked.
Let's refresh our browser to see the updated app in action. Pretty cool, yes!?
> **Note:** The `.bind` command uses the default binding behavior for any property. The default is one-way binding (model to view) for everything except form controls, which default to two-way. You can always override this by using the explicit binding commands `.one-way`, `.two-way` and `.one-time`. Similarly, you can use `.delegate` for event delegation in place of `.trigger`.
## Adding Navigation
A one page app isn't very interesting. We should probably add some more screens and set up a client-side router, don't you think? Let's begin by renaming our _app.js_ and _app.html_ to _welcome.js_ and _welcome.html_ respectively. This will be the first screen of our multi-screen app. Now, lets create a new _app.js_ and _app.html_ which will serve as our "layout", "master page" or "root component". The view will contain our navigation UI and the content placeholder for the current screen and the view-model will configure a router instance with our routes. We'll start with the view-model so you can see how to set up the router:
export class App {
configureRouter(config, router) {
config.title = 'Aurelia';
config.map([
{ route: ['','welcome'], name: 'welcome', moduleId: 'welcome', nav: true, title:'Welcome' }
]);
this.router = router;
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router) {
config.title = 'Aurelia';
config.map([
{ route: ['','welcome'], name: 'welcome', moduleId: 'welcome', nav: true, title:'Welcome' }
]);
this.router = router;
}
}
Ok, we want to use the router, so we begin by creating and exporting our _App_ class and having it implement the `configureRouter` callback. This callback will be invoked with a configuration object. Using the configuration object, we set a title to use when generating the document's title, then map our routes. Each route has the following properties:
* `route`: This is a pattern which, when matched, will cause the router to navigate to this route. You can use static routes like above, but you can also use parameters like this: `customer/:id`. There's also support for wildcard routes and query string parameters. The route can be a single string pattern or an array of patterns as above.
* `name`: This is a name to use in code when generating URLs for the route.
* `moduleId`: This is a path which specifies the component you want to render for this route.
* `title`: You can optionally provide a title to be used in generating the document's title.
* `nav`: If this route should be included in the _navigation model_ because you want to generate a UI with it, set this to true (or a number indicating order).
Following our simple app-building convention, the `App` class will be databound to the above view in _app.html_. A large part of this markup deals with setting up the main navigation structure. But before we get into that...do you see the `require` elements at the top of the view? In the same way that you can use `import` statements in ES2015/2016, Aurelia enables you to use `require` elements in HTML. The require element enables loading and including of functionality such as custom elements and behaviors, and in this case, CSS. Here, since our sample app is going to use some bootstrap styles and icons from font-awesome, we "require" those style sheets. With that in place, we can use bootstrap to lay out our navigation structure, as above. But that's not the interesting part of this view. What we really want to focus on is the binding and custom elements...
Since you've seen basic binding and string interpolation already, let's focus on the new stuff. Take a look at the navbar-nav `ul` element. Its `li` demonstrates how to use a repeater with the following expression `repeat.for="row of router.navigation"`. This will create one `li` for each item in the `router.navigation` array. The local variable is _row_ and you can see that used throughout the `li` and its child elements.
> **Note:** The `navigation` property on the router is an array populated with all the routes you marked as `nav:true` in your route config. Aurelia models its `repeat.for` syntax after the new standard ES2015 `for..of` loop. So, you can think of looping over the array of navigable routes and generating UI for each.
Also on the `li` you can see a demonstration of how to use string interpolation to dynamically add/remove classes. Further down in the view, there's a second `ul`. See the binding on its single child `li`? `if.bind="router.isNavigating"` This conditionally adds/removes the `li` based on the value of the bound expression. Conveniently, the router will update its `isNavigating` property whenever it is....navigating.
The last piece we want to look at is the `router-view` custom element near the bottom of the view. This element, provided by Aurelia, represents the location in the DOM where the current "screen" will be rendered, based on the configured router's state.
With this in place, go ahead and refresh the browser and have a look. You should now see a main navigation with a single selected tab for our "welcome" route. The _welcome_ view should display in the main content area and function as before. Open up the browser's debug tools and have a look at the live DOM. You will see that the _welcome_ view content is displayed inside the `router-view`.
## Adding a Second Page
Well, we've technically got a navigation application now...but it's not very interesting because there's still only one screen. Let's add a second screen. Can you guess how to do it? I bet you can...
Let's display some users from Github. To do that, let's first configure our router for the hypothetical screen:
export class App {
configureRouter(config, router){
config.title = 'Aurelia';
config.map([
{ route: ['','welcome'], name: 'welcome', moduleId: 'welcome', nav: true, title:'Welcome' },
{ route: 'users', name: 'users', moduleId: 'users', nav: true, title:'Github Users' }
]);
this.router = router;
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router) {
config.title = 'Aurelia';
config.map([
{ route: ['','welcome'], name: 'welcome', moduleId: 'welcome', nav: true, title:'Welcome' },
{ route: 'users', name: 'users', moduleId: 'users', nav: true, title:'Github Users' }
]);
this.router = router;
}
}
If you guessed that we need to create a _users.js_ and _users.html_ file, you are correct. Here's the source:
import {HttpClient} from 'aurelia-fetch-client';
import 'fetch';
export class Users {
static inject() { return [HttpClient]; }
constructor(http) {
this.http = http;
this.heading = 'Github Users';
this.users = [];
http.configure(config => {
config
.useStandardConfiguration()
.withBaseUrl('https://api.github.com/');
});
}
activate() {
return this.http.fetch('users')
.then(response => response.json())
.then(users => this.users = users);
}
});
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
import 'fetch';
@inject(HttpClient)
export class Users {
heading = 'Github Users';
users = [];
constructor(http) {
http.configure(config => {
config
.useStandardConfiguration()
.withBaseUrl('https://api.github.com/');
});
this.http = http;
}
activate() {
return this.http.fetch('users')
.then(response => response.json())
.then(users => this.users = users);
}
}
import {autoinject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
import 'fetch';
@autoinject
export class Users {
heading: string = 'Github Users';
users: any[] = [];
constructor(private http: HttpClient) {
http.configure(config => {
config
.useStandardConfiguration()
.withBaseUrl('https://api.github.com/');
});
this.http = http;
}
activate() {
return this.http.fetch('users')
.then(response => response.json())
.then(users => this.users = users);
}
}
There's a lot of cool stuff here. Let's start at the beginning. We are importing `HttpClient` from Aurelia's Fetch plugin, as well as the fetch polyfill. This lets us make HTTP requests in a very simple way, based on the upcoming W3C Fetch standard. This plugin is not included with the default Aurelia configuration but we've included it in this starter kit for you.
> **Note:** In a later tutorial we'll talk more about the power of the integrated package manager and loader.
If you are using ES2016, take a look at the `inject` decorator. What does that do? Well, Aurelia creates the UI components as needed to render your app. It does this by using a [Dependency Injection](http://en.wikipedia.org/wiki/Dependency_injection) container capable of providing constructor dependencies like HttpClient. How does the DI system know what to provide? All you have to do is add that ES2016 `inject` decorator to your class. It should pass a list of types to provide instances of. There should be one argument for each constructor parameter. In the above example, we needed an HttpClient instance, so we added the `HttpClient` type in the `inject` decorator and then added a corresponding parameter in the constructor.
If you are sticking with ES2015, or don't want to use decorators, you can also add a static `inject` method or property to the class that returns an array of types to inject.
If you are using TypeScript >= 1.5, you can add the `@autoinject` decorator to your class and leave out the Types in the decorator call, but just use them on the constructor's signature.
Aurelia's router enforces a lifecycle on view-models whenever routes change. This is referred to as the "Screen Activation Lifecycle" or "Navigation Lifecycle". View-models can optionally hook into various parts of the lifecycle to control flow into and out of the route. When your route is ready to activate the router will call the `activate` hook, if present. In the above code, we use this hook to call the GitHub API and get some users back. Notice that we return the result of the http request back from our `activate` method. All the `HttpClient` APIs return a `Promise`. The router will detect a `Promise` and wait to complete navigation until after it resolves. So, in this way, you can optionally force the router to delay displaying the page until it is populated with data.
>**Note:** The full navigation lifecycle includes `canActivate`, `activate`, `canDeactivate` and `deactivate` hooks. The can* methods can return a boolean (or Promise of boolean) to accept or reject the transition into or out of the current screen.
>**Note:** If you aren't familiar with [Promises](http://www.html5rocks.com/en/tutorials/es6/promises/), these are a new feature of ES2015 designed to improve asynchronous programming. A `Promise` is an object that represents a future result. Essentially, it represents a "promise" to complete some work or to provide some data at some point in the future.
The view for this screen is pretty straight forward. There's nothing you haven't seen before.
Once you've got all this in place, go ahead and run your app again. You should now see two items in the nav bar and be able to switch back and forth between them. Huzzah!
Let's recap. To add a page to your app:
1. Add the route configuration to the _app.js_ router.
2. Add a view-model.
3. Add a view with the same name (but with an .html extension).
4. Celebrate.
 I see you're interested in learning some extra awesome on this fine day. In that case, let's create a custom HTML element. I think a good candidate for this is our navbar. That's a lot of HTML in our _app.html_ file. Why not extract a custom `` element to make things a bit more declarative? Here's what we want to be able to write in the end:
This code requires a `nav-bar` element from "nav-bar" and once it's available in the view, we can use it like any other element, including databinding to its custom properties (like _router_). So, how do we get to this end product?
Guess what? Our simple view-model/view conventions still apply for custom elements. Let's create a _nav-bar.js_ and a _nav-bar.html_. Here's the code for the view-model first:
import {bindable, decorators} from 'aurelia-framework';
export let NavBar = decorators(bindable('router')).on(class {
constructor() {
this.router = null;
}
});
import {bindable} from 'aurelia-framework';
export class NavBar {
@bindable router = null;
}
import {bindable} from 'aurelia-framework';
import {Router} from 'aurelia-router';
export class NavBar {
@bindable router: Router = null;
}
To create a custom element, you create and export a class. Since this class is going to be used in HTML as an element, we need to tell the framework what properties on the class should appear as attributes on the element. To do that, we use the _bindable_ decorator. Like _inject_, _bindable_ is a way to provide information about your class to the Aurelia framework. Aurelia is smart and can infer many things, but when it can't or when you want to do something different than the conventions, you supply some additional information through decorators. The `bindable` decorator tells the framework that we want our class's `router` property to be surfaced as an attribute in the HTML. Once it's surfaced as an attribute, we can bind to it in the view.
This looks almost identical to the navbar HTML in our original _app.html_ file. We've basically extracted that and put it into this template. Instead of binding to _app.js_ though, it's now binding to _nav-bar.js_.
This is a very simple custom element with no real behavior, but it is complete and usable as shown above.
Wait! I know what you are thinking. This custom element is so simple...it seems a bit silly to require a JavaScript class just to define the single `router` property. Couldn't we get rid of that somehow? Well, the answer is YES. For very simple elements which have no behavior but simply provide a view that can be bound to a set of properties, we can omit the JavaScript file altogether. Let's see how that works.
First, delete the _nav-bar.js_ file. Next, we need to make one change to the _nav-bar.html_ file. On the template element, we can declare the bindable properties of our element like this:
...
We can have more than one property by separating them by commas. Finally, we need to update our _app.html_ file so that the `require` element points to our html component. Here's what it should look like:
> **Note:** Anything required into a view with the `require` element is local to the view. As a result, you don't have to worry about name conflicts. You can also load app-wide elements and other behaviors for convenience so you don't have to require common resources in every view.
You may wonder how Aurelia determines the name of the custom element. By convention, it will use the export name of the class, lowered and hyphenated. In our html-only scenario, it will use the file name.
In addition to creating custom elements, you can also create custom attributes which add new behavior to existing elements. On occasion you may even need an attribute to dynamically control templates by adding and removing DOM from the view, like the `repeat` and `if` we used above. You can do all that and much more with Aurelia's powerful and extensible templating engine. Here's a secret...none of Aurelia's so-called "built in" behaviors are actually built in. They are in their own library and are "installed" into Aurelia as a plugin. We provide our built-ins using the same core that you have to build your own apps and plugins.
## Bonus: Leveraging Child Routers
Can't get enough can you? Well, I've got a treat for you. Let's add a third page to our app...with its own router!  Child routers have their own route configuration and navigate relative to the parent router. Prepare thyself for insanity....
First, let's update our _app.js_ with the new configuration. Here's what it should look like:
export class App {
configureRouter(config, router) {
config.title = 'Aurelia';
config.map([
{ route: ['','welcome'], name: 'welcome', moduleId: 'welcome', nav: true, title:'Welcome' },
{ route: 'users', name: 'users', moduleId: 'users', nav: true, title:'Github Users' },
{ route: 'child-router', name: 'child-router', moduleId: 'child-router', nav: true, title:'Child Router' }
]);
this.router = router;
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router) {
config.title = 'Aurelia';
config.map([
{ route: ['','welcome'], name: 'welcome', moduleId: 'welcome', nav: true, title:'Welcome' },
{ route: 'users', name: 'users', moduleId: 'users', nav: true, title:'Github Users' },
{ route: 'child-router', name: 'child-router', moduleId: 'child-router', nav: true, title:'Child Router' }
]);
this.router = router;
}
}
Nothing new there. The interesting part is what's inside _child-router.js_...
export class ChildRouter {
heading = 'Child Router';
configureRouter(config, router){
config.map([
{ route: ['','welcome'], name: 'welcome', moduleId: 'welcome', nav: true, title:'Welcome' },
{ route: 'users', name: 'users', moduleId: 'users', nav: true, title:'Github Users' },
{ route: 'child-router', name: 'child-router', moduleId: 'child-router', nav: true, title:'Child Router' }
]);
this.router = router;
}
}
export class ChildRouter {
constructor() {
this.heading = 'Child Router';
}
configureRouter(config, router) {
config.map([
{ route: ['','welcome'], name: 'welcome', moduleId: 'welcome', nav: true, title:'Welcome' },
{ route: 'users', name: 'users', moduleId: 'users', nav: true, title:'Github Users' },
{ route: 'child-router', name: 'child-router', moduleId: 'child-router', nav: true, title:'Child Router' }
]);
this.router = router;
}
}
import {RouterConfiguration, Router} from 'aurelia-router';
export class ChildRouter {
heading: string = 'Child Router';
router: Router;
configureRouter(config: RouterConfiguration, router: Router) {
config.map([
{ route: ['','welcome'], name: 'welcome', moduleId: 'welcome', nav: true, title:'Welcome' },
{ route: 'users', name: 'users', moduleId: 'users', nav: true, title:'Github Users' },
{ route: 'child-router', name: 'child-router', moduleId: 'child-router', nav: true, title:'Child Router' }
]);
this.router = router;
}
}
What!? It's practically the same configuration as `App`? What? Why? Well...you should probably never do this in real life...but it's pretty cool what this does. This, my friends, is a recursive router, and we're doing it because we can.
For completeness, here's the view:
Run the app and see the magic....and pray the universe doesn't explode.
## Conclusion
With its strong focus on developer experience, Aurelia can enable you to not only create amazing applications, but also enjoy the process. We've designed it with simple conventions in mind so you don't need to waste time with tons of configuration or write boilerplate code just to satisfy a stubborn or restrictive framework. You'll never hit a roadblock with Aurelia either. It's been carefully designed to be pluggable and customizable.
Thanks for taking the time to read through our guide. We hope you'll explore the docs and build something awesome. We look forward to seeing what you will make.


A Master of Business Administration (MBA; also Master's in Business Administration) is a graduate degree focusing on business administration and investment management.[1] The core courses in an MBA program cover various areas of business administration such as accounting, applied statistics, human resources, business communication, business ethics, business law, business strategy, finance, managerial economics, management, entrepreneurship, marketing, supply-chain management, and operations management in a manner most relevant to management analysis and strategy. It originated in the United States in the early 20th century when the country industrialized and companies sought scientific management.[2] Most programs also include elective courses and concentrations for further study in a particular area, for example, accounting, finance, marketing, and human resources. MBA programs in the United States typically require completing about forty to sixty credits (sixty to ninety in a quarter system), much higher than the thirty credits (thirty-six to forty-five in a quarter system) typically required for degrees that cover some of the same material such as the Master of Economics, Master of Finance, Master of Accountancy, Master of Science in Marketing and Master of Science in Management. The MBA is a terminal degree and a professional degree.[3][4] Accreditation bodies specifically for MBA programs ensure consistency and quality of education. Business schools in many countries offer programs tailored to full-time, part-time, executive (abridged coursework typically occurring on nights or weekends) and distance learning students, many with specialized concentrations.
CEO & Founder
Some text that describes me lorem ipsum ipsum lorem.
<EMAIL_ADDRESS>Art Director
Some text that describes me lorem ipsum ipsum lorem.
<EMAIL_ADDRESS>Designer
Some text that describes me lorem ipsum ipsum lorem.
john@example.com


Basic box shadow example
Warning
: The thermostat on the cosmic transcender has reached a critical level.
Multiple box shadows example
Warning
: The thermostat on the cosmic transcender has reached a critical level.
Inner shadows example
Press me!


 Please use SQRL link/QR code below to associate SQRL identity with your account.
{% else %}Congratulations! You already have SQRL identity associated with your account. If you would like to either change or delete existing SQRL identity associated with your account, you can do that by selecting appropriate option in your SQRL client and then using the SQRL link/QR code below.
Note: For both changing or deleting your SQRL identity, you will need to load your SQRL rescue code.
Caution: Normally it is not advised to change or delete your SQRL identity. Usually these operations are only required when SQRL identity is compromised.
{% endif %} {% sqrl as sqrl %} {% endblock %} {% block scripts %} {% endblock %} {% comment %} **sqrl/manage.html** Used to manage SQRL identity in relation to existing logged in account. When no SQRL identity is already associated with the account, it can be used to associate a new identity. When existing SQRL is already associated, this page can be used to either change or delete existing identity. No special variables are passed in the context for the association to work. All necessary data should already be in the context. {% endcomment %}


My Story!
Hi there! My name's Cass Aleatory (or just Cassie if you prefer). You might also know me as @rk_firestorm, the handle I use across all my social media platforms. I'm an independent game developer and writer. While telling stories is my specialty, I'm also a pretty experienced programmer who's worked in C#, Java, C++, HTML, CSS, and Processing (Java based). In my spare time, I enjoy experimenting with other game development roles such as music composition, sound design, graphics art, community management, and user experience design.
I graduated from Verona Area High School in the spring of 2017, and am currently working towards two Bachelor's degrees. I'm a triple major in English: Professional Writing & Publishing, Media Arts & Game Development: Technology, and Film Studies at the University of Wisconsin in Whitewater. I'm also a member of the University Honors Program there.
Nowadays, I work mostly on projects of my own choosing that have personal significance to me. My stories tend to be character-based narratives that deal with themes of emotional and mental struggle, and how love, friendship, and communication are affected by the things that go on inside people's heads. I also enjoy writing metagame and nonlinear stories from time to time.
I'm currently hard at work on several large projects, including the character-driven, decisions-matter, multiple-act Exarch and End of The Party, a randomized-yet-decision-based light-hearted RPG. Even though I'm really stressed out 110% of the time, I'm always keeping my eyes open for even more projects that offer me the chance to tell cool stories. If you want to hire or work with me, please do reach out through any of the platforms linked in the footer below. Click "Contact Me" in the navbar to be taken directly there!


The BlmcJointModule class is containing the joint information.
More...
#include <blmc_joint_module.hpp>
|
| | BlmcJointModule (std::shared_ptr< blmc_drivers::MotorInterface > motor, const double &motor_constant, const double &gear_ratio, const double &zero_angle, const bool &reverse_polarity=false, const double &max_current=2.1) |
| | Construct a new BlmcJointModule object. More...
|
| |
| void | set_torque (const double &desired_torque) |
| | Set the joint torque to be sent. More...
|
| |
| void | set_zero_angle (const double &zero_angle) |
| | Set the zero_angle. More...
|
| |
| void | set_joint_polarity (const bool &reverse_polarity) |
| | Define if the motor should turn clock-wize or counter clock-wize. More...
|
| |
| void | send_torque () |
| | send the joint torque to the motor. More...
|
| |
| double | get_max_torque () const |
| | Get the maximum admissible joint torque that can be applied. More...
|
| |
| double | get_sent_torque () const |
| | Get the sent joint torque. More...
|
| |
| double | get_measured_torque () const |
| | Get the measured joint torque. More...
|
| |
| double | get_measured_angle () const |
| | Get the measured angle of the joint. More...
|
| |
| double | get_measured_velocity () const |
| | Get the measured velocity of the joint. More...
|
| |
| double | get_measured_index_angle () const |
| | Get the measured index angle. More...
|
| |
| double | get_zero_angle () const |
| | Get the zero_angle_. More...
|
| |
| void | set_position_control_gains (double kp, double kd) |
| | Set control gains for PD position controller. More...
|
| |
| double | execute_position_controller (double target_position_rad) const |
| | Execute one iteration of the position controller. More...
|
| |
| bool | calibrate (double &angle_zero_to_index, double &index_angle, bool mechanical_calibration=false) |
| | This method calibrate the joint position knowing the angle between the closest (in positive torque) motor index and the theoretical zero pose. More...
|
| |
| void | init_homing (int joint_id, double search_distance_limit_rad, double home_offset_rad, double profile_step_size_rad=0.001) |
| | Initialize the homing procedure. More...
|
| |
| HomingReturnCode | update_homing () |
| | Perform one step of homing on encoder index. More...
|
| |
| double | get_distance_travelled_during_homing () const |
| | Get distance between start and end position of homing. More...
|
| |
|
|
std::shared_ptr< blmc_drivers::MotorInterface > | motor_ |
| | This is the pointer to the motor interface.
|
| |
|
double | motor_constant_ |
| | This is the torque constant of the motor: .
|
| |
| double | gear_ratio_ |
| | This correspond to the reduction ( ) between the motor rotation and the joint. More...
|
| |
|
double | zero_angle_ |
| | This is the distance between the closest positive index and the zero configuration.
|
| |
|
double | polarity_ |
| | This change the motor rotation direction.
|
| |
| double | max_current_ |
| | This is the maximum current we can apply during one experiment. More...
|
| |
|
double | position_control_gain_p_ |
| | P gain of the position PD controller.
|
| |
|
double | position_control_gain_d_ |
| | D gain of the position PD controller.
|
| |
|
struct HomingState | homing_state_ |
| |
The BlmcJointModule class is containing the joint information.
It is here to help converting the data from the motor side to the joint side. It also allows the calibration of the joint position during initialization.
◆ BlmcJointModule()
| blmc_robots::BlmcJointModule::BlmcJointModule |
( |
std::shared_ptr< blmc_drivers::MotorInterface > |
motor, |
|
|
const double & |
motor_constant, |
|
|
const double & |
gear_ratio, |
|
|
const double & |
zero_angle, |
|
|
const bool & |
reverse_polarity = false , |
|
|
const double & |
max_current = 2.1 |
|
) |
| |
Construct a new BlmcJointModule object.
- Parameters
-
| motor | is the C++ object allowing us to send commands and receive sensor data. |
| motor_constant | ( ) is the torque constant of the motor |
| gear_ratio | is the gear ratio between the motor and the joint. |
| zero_angle | is the angle between the closest positive motor index and the zero configuration. |
| reverse_polarity | |
| max_current | |
◆ calibrate()
| bool blmc_robots::BlmcJointModule::calibrate |
( |
double & |
angle_zero_to_index, |
|
|
double & |
index_angle, |
|
|
bool |
mechanical_calibration = false |
|
) |
| |
This method calibrate the joint position knowing the angle between the closest (in positive torque) motor index and the theoretical zero pose.
- Deprecated:
- !!!!!!!
Warning, this method should be called in a real time thread!
- Parameters
-
◆ execute_position_controller()
| double blmc_robots::BlmcJointModule::execute_position_controller |
( |
double |
target_position_rad | ) |
const |
Execute one iteration of the position controller.
- Parameters
-
| target_position_rad | Target position (rad). |
- Returns
- Torque command (Nm).
◆ get_distance_travelled_during_homing()
| double blmc_robots::BlmcJointModule::get_distance_travelled_during_homing |
( |
| ) |
const |
Get distance between start and end position of homing.
Compute the distance that the joint moved between initialization of homing and reaching the home position.
This can be used to determine the home offset by first moving the joint to the desired zero position, then executing the homing and finally calling this function which will provide the desired home offset.
- Returns
- Distance between start and end position of homing.
◆ get_max_torque()
| double blmc_robots::BlmcJointModule::get_max_torque |
( |
| ) |
const |
Get the maximum admissible joint torque that can be applied.
- Returns
- double
◆ get_measured_angle()
| double blmc_robots::BlmcJointModule::get_measured_angle |
( |
| ) |
const |
Get the measured angle of the joint.
- Returns
- double (rad).
◆ get_measured_index_angle()
| double blmc_robots::BlmcJointModule::get_measured_index_angle |
( |
| ) |
const |
Get the measured index angle.
There is one index per motor rotation so there are gear_ratio indexes per joint rotation.
- Returns
- double (rad).
◆ get_measured_torque()
| double blmc_robots::BlmcJointModule::get_measured_torque |
( |
| ) |
const |
Get the measured joint torque.
- Returns
- double (Nm).
◆ get_measured_velocity()
| double blmc_robots::BlmcJointModule::get_measured_velocity |
( |
| ) |
const |
Get the measured velocity of the joint.
This data is computed on board of the control card.
- Returns
- double (rad/s).
◆ get_motor_measurement()
| double blmc_robots::BlmcJointModule::get_motor_measurement |
( |
const mi & |
measurement_id | ) |
const |
|
private |
Get motor measurements and check if there are data or not.
- Parameters
-
| measurement_id | is the id of the measurement you want to get. check: blmc_drivers::MotorInterface::MeasurementIndex |
- Returns
- double the measurement.
◆ get_motor_measurement_index()
| long int blmc_robots::BlmcJointModule::get_motor_measurement_index |
( |
const mi & |
measurement_id | ) |
const |
|
private |
Get the last motor measurement index for a specific data.
If there was no data yet, return NaN
- Parameters
-
| measurement_id | is the id of the measurement you want to get. check: blmc_drivers::MotorInterface::MeasurementIndex |
- Returns
- double the measurement.
◆ get_sent_torque()
| double blmc_robots::BlmcJointModule::get_sent_torque |
( |
| ) |
const |
Get the sent joint torque.
- Returns
- double (Nm).
◆ get_zero_angle()
| double blmc_robots::BlmcJointModule::get_zero_angle |
( |
| ) |
const |
Get the zero_angle_.
These are the angle between the starting pose and the theoretical zero pose.
- Returns
- double (rad).
◆ init_homing()
| void blmc_robots::BlmcJointModule::init_homing |
( |
int |
joint_id, |
|
|
double |
search_distance_limit_rad, |
|
|
double |
home_offset_rad, |
|
|
double |
profile_step_size_rad = 0.001 |
|
) |
| |
Initialize the homing procedure.
This has to be called before update_homing().
- Parameters
-
| joint_id | ID of the joint. This is only used for debug prints. |
| search_distance_limit_rad | Maximum distance the motor moves while searching for the encoder index. Unit: radian. |
| home_offset_rad | Offset from home position to zero position. Unit: radian. |
| profile_step_size_rad | Distance by which the target position of the position profile is changed in each step. Set to a negative value to search for the next encoder index in negative direction. Unit: radian. |
◆ joint_torque_to_motor_current()
| double blmc_robots::BlmcJointModule::joint_torque_to_motor_current |
( |
double |
torque | ) |
const |
|
private |
Convert from joint torque to motor current.
- Parameters
-
| [in] | torque | is the input joint |
- Returns
- double the equivalent motor current.
◆ motor_current_to_joint_torque()
| double blmc_robots::BlmcJointModule::motor_current_to_joint_torque |
( |
double |
current | ) |
const |
|
private |
Convert from motor current to joint torque.
- Parameters
-
| current | is the motor current. |
- Returns
- double is the equivalent joint torque.
◆ send_torque()
| void blmc_robots::BlmcJointModule::send_torque |
( |
| ) |
|
send the joint torque to the motor.
The conversion between joint torque and motor current is done automatically.
◆ set_joint_polarity()
| void blmc_robots::BlmcJointModule::set_joint_polarity |
( |
const bool & |
reverse_polarity | ) |
|
Define if the motor should turn clock-wize or counter clock-wize.
- Parameters
-
| reverse_polarity | true:reverse rotation axis, false:do nothing. |
◆ set_position_control_gains()
| void blmc_robots::BlmcJointModule::set_position_control_gains |
( |
double |
kp, |
|
|
double |
kd |
|
) |
| |
Set control gains for PD position controller.
- Parameters
-
| kp | P gain ( (Nm) / rad ). |
| kd | D gain ( (Nm) / (rad/s) ). |
◆ set_torque()
| void blmc_robots::BlmcJointModule::set_torque |
( |
const double & |
desired_torque | ) |
|
Set the joint torque to be sent.
- Parameters
-
◆ set_zero_angle()
| void blmc_robots::BlmcJointModule::set_zero_angle |
( |
const double & |
zero_angle | ) |
|
Set the zero_angle.
The zero_angle is the angle between the closest positive motor index and the zero configuration.
- Parameters
-
◆ update_homing()
Perform one step of homing on encoder index.
Searches for the next encoder index in positive direction and, when found, sets it as home position.
Only performs one step, so this method needs to be called in a loop. This method only set the control, one MUST send the control for the motor after calling this method.
The motor is moved with a position profile until either the encoder index is reached or the search distance limit is exceeded. The position is controlled with a simple PD controller.
If the encoder index is found, its position is used as home position. The zero position is offset from the home position by adding the "home
offset" to it (i.e. zero = home pos. + home offset). If the search distance limit is reached before the encoder index occurs, the homing fails.
- Returns
- Status of the homing procedure.
◆ gear_ratio_
| double blmc_robots::BlmcJointModule::gear_ratio_ |
|
private |
This correspond to the reduction ( ) between the motor rotation and the joint.
◆ max_current_
| double blmc_robots::BlmcJointModule::max_current_ |
|
private |
This is the maximum current we can apply during one experiment.
The program shut down if this value is achieved.
The documentation for this class was generated from the following files:


This Bible contradiction is from the Skeptic's Annotated Bible.
Verses
Salvation is by faith alone.
Mark 16:16 View context
He that believeth and is baptized shall be saved; but he that believeth not shall be damned.
John 3:18 View context
He that believeth on him is not condemned: but he that believeth not is condemned already, because he hath not believed in the name of the only begotten Son of God.
Acts 16:30-31 View context
And brought them out, and said, Sirs, what must I do to be saved?
And they said, Believe on the Lord Jesus Christ, and thou shalt be saved, and thy house.
Romans 1:16-17 View context
For I am not ashamed of the gospel of Christ: for it is the power of God unto salvation to every one that believeth; to the Jew first, and also to the Greek.
For therein is the righteousness of God revealed from faith to faith: as it is written, The just shall live by faith.
Romans 3:20 View context
Therefore by the deeds of the law there shall no flesh be justified in his sight: for by the law [is] the knowledge of sin.
Romans 4:2 View context
For if Abraham were justified by works, he hath [whereof] to glory; but not before God.
Romans 4:13 View context
For the promise, that he should be the heir of the world, [was] not to Abraham, or to his seed, through the law, but through the righteousness of faith.
Romans 5:1 View context
Therefore being justified by faith, we have peace with God through our Lord Jesus Christ:
Romans 10:9 View context
That if thou shalt confess with thy mouth the Lord Jesus, and shalt believe in thine heart that God hath raised him from the dead, thou shalt be saved.
Galatians 2:16 View context
Knowing that a man is not justified by the works of the law, but by the faith of Jesus Christ, even we have believed in Jesus Christ, that we might be justified by the faith of Christ, and not by the works of the law: for by the works of the law shall no flesh be justified.
Galatians 3:11-12 View context
But that no man is justified by the law in the sight of God, [it is] evident: for, The just shall live by faith.
And the law is not of faith: but, The man that doeth them shall live in them.
Ephesians 2:8-9 View context
For by grace are ye saved through faith; and that not of yourselves: [it is] the gift of God:
Not of works, lest any man should boast.
Titus 3:5 View context
Not by works of righteousness which we have done, but according to his mercy he saved us, by the washing of regeneration, and renewing of the Holy Ghost;
Salvation is not by faith alone.
Psalm 62:12 View context
Also unto thee, O Lord, [belongeth] mercy: for thou renderest to every man according to his work.
Proverbs 10:16 View context
The labour of the righteous [tendeth] to life: the fruit of the wicked to sin.
Ecclesiastes 12:14 View context
For God shall bring every work into judgment, with every secret thing, whether [it be] good, or whether [it be] evil.
Jeremiah 17:10 View context
I the LORD search the heart, [I] try the reins, even to give every man according to his ways, [and] according to the fruit of his doings.
Ezekiel 18:27 View context
Again, when the wicked [man] turneth away from his wickedness that he hath committed, and doeth that which is lawful and right, he shall save his soul alive.
Matthew 5:20 View context
For I say unto you, That except your righteousness shall exceed [the righteousness] of the scribes and Pharisees, ye shall in no case enter into the kingdom of heaven.
Matthew 12:37 View context
For by thy words thou shalt be justified, and by thy words thou shalt be condemned.
Matthew 16:27 View context
For the Son of man shall come in the glory of his Father with his angels; and then he shall reward every man according to his works.
Matthew 19:17 View context
And he said unto him, Why callest thou me good? [there is] none good but one, [that is], God: but if thou wilt enter into life, keep the commandments.
Matthew 25:41-46 View context
Then shall he say also unto them on the left hand, Depart from me, ye cursed, into everlasting fire, prepared for the devil and his angels:
For I was an hungred, and ye gave me no meat: I was thirsty, and ye gave me no drink:
I was a stranger, and ye took me not in: naked, and ye clothed me not: sick, and in prison, and ye visited me not.
Then shall they also answer him, saying, Lord, when saw we thee an hungred, or athirst, or a stranger, or naked, or sick, or in prison, and did not minister unto thee?
Then shall he answer them, saying, Verily I say unto you, Inasmuch as ye did [it] not to one of the least of these, ye did [it] not to me.
And these shall go away into everlasting punishment: but the righteous into life eternal.
Luke 10:26-28 View context
He said unto him, What is written in the law? how readest thou?

And he said unto him, Thou hast answered right: this do, and thou shalt live.
John 5:29 View context
And shall come forth; they that have done good, unto the resurrection of life; and they that have done evil, unto the resurrection of damnation.
Romans 2:6 View context
Who will render to every man according to his deeds:
2 Corinthians 5:10 View context
For we must all appear before the judgment seat of Christ; that every one may receive the things [done] in [his] body, according to that he hath done, whether [it be] good or bad.
2 Corinthians 11:15 View context
Therefore [it is] no great thing if his ministers also be transformed as the ministers of righteousness; whose end shall be according to their works.
Philippians 2:12 View context
Wherefore, my beloved, as ye have always obeyed, not as in my presence only, but now much more in my absence, work out your own salvation with fear and trembling.
James 1:22 View context
But be ye doers of the word, and not hearers only, deceiving your own selves.
James 2:14 View context
What [doth it] profit, my brethren, though a man say he hath faith, and have not works? can faith save him?
1 Peter 1:17 View context
And if ye call on the Father, who without respect of persons judgeth according to every man's work, pass the time of your sojourning [here] in fear:
Revelation 2:23 View context
And I will kill her children with death; and all the churches shall know that I am he which searcheth the reins and hearts: and I will give unto every one of you according to your works.
Revelation 20:12-13 View context
And I saw the dead, small and great, stand before God; and the books were opened: and another book was opened, which is [the book] of life: and the dead were judged out of those things which were written in the books, according to their works.

Revelation 22:14 View context
Blessed [are] they that do his commandments, that they may have right to the tree of life, and may enter in through the gates into the city.


--- type: formControls title: Flat select usage: A flat single select control. stylesheet: '/scss/controls/flat-select.scss' ---


Source code for rest_api_framework.partials
"""
Enable partials response from the api. With partials response, only a
subset of fields are send back to the request user.
DataStore are responsible for implementing partial options
"""
[docs]class Partial(object):
"""
The base implementation of partial response.
"""
def __init__(self, partial_keyword="fields"):
self.partial_keyword = partial_keyword
[docs] def get_partials(self, **kwargs):
"""
This partial implementation wait for a list of fields
separated by comma. Other implementations are possible. Just
inherit from this base class and implement your own
get_partials method.
get_partials does not check that the fields are part of the
model. Datastore get_list will check for it and raise an error
if needed.
"""
partial_fields = []
if kwargs.get(self.partial_keyword):
partial_fields = kwargs.pop(self.partial_keyword).split(',')
return partial_fields, kwargs


|
My Project
|
Files | |
| file | board.c |
| file | board.h [code] |
| file | cursor.c |
| file | cursor.h [code] |
| file | cursor_pixmap.h [code] |
| file | font.c |
| file | font.h [code] |
| file | game.c |
| file | game.h [code] |
| file | i8042.h [code] |
| file | i8254.h [code] |
| file | keyboard.c |
| file | keyboard.h [code] |
| file | menu.c |
| file | menu.h [code] |
| file | mouse.c |
| file | mouse.h [code] |
| file | packet.c |
| file | packet.h [code] |
| file | player.c |
| file | player.h [code] |
| file | proj.c |
| file | realtimeclock.h [code] |
| file | rtc.c |
| file | rtc.h [code] |
| file | score.c |
| file | score.h [code] |
| file | timer.c |
| file | timer.h [code] |
| file | video.c |
| file | video.h [code] |
| file | xpm.c |
| file | xpm.h [code] |


---
layout: post
title: Rough sketch for an illustration
date: '2013-10-09T17:31:20+02:00'
categories:
- Sketches
picture_url: https://film-Storyboards.tumblr.com/post/63591841533/rough-sketch-for-an-Illustration
---
Rough sketch for an Illustration.


Home
Sleeping
Eating
Vitals
Logout
Welcome
Feeding History
Keep track of your baby's feeding progress
Feeding Entries
Eating Start
Eating End
Duration (min.)
Last 10 feedings


Sports Hall for the Max-Planck School / Loewer + Partner Architekten
- Curated by Paula Pintos
- about 18 hours
Extension · Rüsselsheim am Main, Germany
- version="1.0" encoding="UTF-8"? Architects: Loewer + Partner Architekten
- AreaArea of this architecture projectArea: 1900.0 m2
- version="1.0" encoding="UTF-8"?YearCompletion year of this architecture project Year: 2018
- version="1.0" encoding="UTF-8"?PhotographsPhotographs: Ralf Heidenreich
- version="1.0" encoding="UTF-8"?ManufacturersBrands with products used in this architecture project Manufacturers: Benz, DLW, HERAKLITH, Schuco, Trespa Brasil, Villeroy & Boch
- Lead Architects Andreas Janouschkowetz, Roland Meergans, Rudolf Schulz
- Design Team Edmund Kallenbach, Christian Wick, Aslihan Banbal
More Specs Less Specs
Text description provided by the architects. Shed roofs came up during the middle of the 19th century, especially at factory buildings. Rüsselsheim is one of the former industrial cities of Germany, well known for its Opel headquarter. The inspiration for the roof of the sports hall came from a factory building nearby. One of the advantages of Shed roofs is that natural incidence of light, coming from the north, will not produce any drop shadow, nor dazzling the people inside. Another positive effect of the north orientation of the building is that unnecessary heat indoors can be minimized; The direct solar radiation is relatively low.
The individual elements of a Shed roof are called „Reiter“. One “Reiter” consists of a beveled roof area and a steeper or perpendicular window area, which enables the incidence of light. After the demolition of the old gym, the new sports hall from Loewer + Partner Architects will arise on the sites of the Max-Planck School in Rüsselsheim. The new building consists of a sports hall, a multipurpose room, and other secondary rooms. Due to the single-story building, the complete sports hall is disability accessible.
The hall is built out of reinforced concrete construction. The building obtains a “solid base” made out of reinforced concrete and masonry construction with WDVS. The upper building starting from 3,50m is built as a “light crown” with a back ventilated wall cladding out of the galvanized steel sheet. The shed roof consists of wood construction. The colors of the building were chosen on the basis of the surrounding buildings.
To make the floor construction as robust and sustainable as possible we chose a linoleum floor for the sports hall and a ground poured asphalt screed for the hall and the locker rooms. The walls of the sports hall are covered with an acoustic bulging wall up to the high of 3,2m. From there the walls are left in a concrete look. The ceiling of the sports hall consists of a trapezoidal sheet with acoustic advantages.


[mfr] iQuote Universal Country Request
The user "[emailAddress]" has requested that HP Universal be made available in [country] for [mfr].


Hagatna is about 8NM NE of Apra Harbour.
Advice on the difficult reef entrance to Hagatna harbour can be obtained from harbour control on VHF Channel 16.
Fuel and water are available at the fishermen's wharf in the Boat Basin.


Three.js Keyframe
Move Use left mouse or one-finger move to touch
Zoom Use middle mouse/mousewheel or two-finger spread/squish to touch
Pan Use right mouse/ arrow keys or two-finger move to touch


{% extends 'base.html' %} {% block title %}Check Your Inbox{% endblock %} {% block content %}
We have emailed you instructions for resetting your password. Please check your inbox!


A
Levels / New Level
Marina B
Logged in as instructor
New level 2/2: problems
All problems
Swap Swap Swap Swap Swap Swap Swap Swap Swap Swap Swap Swap Swap Swap Swap Swap Swap Swap
Convert units
Draw stars
Draw stars
Draw stars
Draw stars
Draw stars
Draw stars
Draw stars
Draw stars
Draw stars
Draw stars
Draw stars
Draw stars
E
D
Problems
Swap


Item 3.
Do you believe that President Biden's student loan debt forgiveness policy is a good idea? Why or why not? Do its benefits for lower-income debtors outweigh its impats on the U.S. budget? Estimate whether your taxes will rise as a result and, if so, by how much, using a well-explained dimensional analysis.in-context


_The_Celebration_(Festen)_ (R) **** (out of ****)
The title of this Danish import couldn't be more ironic. First, there's the literal level, in the plot: the sixtieth birthday bash of a wealthy patriarch Helge (Hennig Moritzen) becomes something decidedly less festive when his successful eldest son Christian (Ulrich Thomsen) disrupts the event with some shocking allegations about him and Christian's dead twin sister. On a deeper level, there's the irony of tone: while the charges being made by Christian are as serious as they can get, the situation is played for some gleefully venomous laughs--making the overall mood sort of a "celebration" of everyone's misery.
It's a fine line to walk, that between comedy and misery, but director/co-writer (with Mogens Rukov) Thomas Vinterberg handle the balancing act magnificently. While one is often laughing at and with Christian's shamelessly disruptive antics, one can feel the pain that compels him to do so. The real balancing act, though, is how Vinterberg keeps the truth largely ambiguous through his characters; they are portrayed equal parts bad and good, so it's often up to question whether or not Christian is telling the truth or merely spiting his father. It's that heightened sense of reality--in accordance with the Danish film movement called "Dogme 95," which sets standards for realism and "purity"--that lends _Festen_ an uncommon power, even if some "realist" techniques become somewhat irksome (i.e. shaky, cinema-verite hand-held camera work).
Michael Dequina
<EMAIL_ADDRESS>|<EMAIL_ADDRESS>Mr. Brown's Movie Site: http://welcome.to/mrbrown
CompuServe Hollywood Hotline: http://www.HollywoodHotline.com
The review above was posted to the
rec.arts.movies.reviews newsgroup (de.rec.film.kritiken for German reviews).
The Internet Movie Database accepts no responsibility for the contents of the
review and has no editorial control. Unless stated otherwise, the copyright
belongs to the author.
Please direct comments/criticisms of the review to relevant newsgroups.
Broken URLs inthe reviews are the responsibility of the author.
The formatting of the review is likely to differ from the original due
to ASCII to HTML conversion.
Related links: index of all rec.arts.movies.reviews reviews


TL;DR
Learn three ways you can develop HTTP Client SDKs in .NET. Source code: https://github.com/NikiforovAll/http-sdk-guide
- TL;DR
- Key Takeaways
- Introduction
- Writing HTTP Client SDK
- Consuming API Clients
- Extending HTTP Client SDKs. Adding cross-cutting concerns via DelegatingHandler
- Testing HTTP Client SDKs
- Writing HTTP Client SDK. Declarative approach
- Consuming API Clients. Refit
- Writing HTTP Client SDK. Automated approach
- Choosing the right approach
- Summary
- Reference
Key Takeaways
- Writing and maintaining HTTP Client SDKs is a very important skill for modern .NET developers working with distributed systems.
- In order to properly manage HTTP connections, you need to design your API Clients to be ready to be consumed from any Dependency Injection container.
- A good client SDK is composable, providing straightforward ways to configure and extend it.
- Testing HTTP Client SDKs can be very beneficial in certain scenarios and it gives you additional confidence in your code.
- There are many ways of developing HTTP Client SDKs. This article helps you to choose the right one according to your scenario.
Introduction
Today’s cloud-based, microservice-based or internet-of-things applications often depend on communicating with other systems across a network. Each service runs in its process and solves a bounded set of problems. Communication between services is based on a lightweight mechanism, often an HTTP resource API.
From a .NET developer perspective, we want to provide a consistent and manageable way of integrating with a particular service in the form of a distributable package. Preferably, we also want to ship the service integration code we develop as a NuGet package and share it with other people, teams, or even organizations. In this article, I will share many aspects of creating and using HTTP Client SDKs using .NET 6.
Client SDKs provide a meaningful abstraction layer over remote service. Essentially, it allows making Remote Procedure Calls (RPC). The responsibility of a Client SDK is to serialize some data, send it to remote, deserialize and process a response.
The main benefits of API SDKs:
- It speedups the API integration process
- Provides a consistent and standard approach
- Gives somewhat control to service owners over the way APIs are consumed
Writing HTTP Client SDK
Throughout this article, we will write full-functioning Dad Jokes API Client. It serves dad jokes, let’s have some fun. It is a good idea to start from the contract.
public interface IDadJokesApiClient
{
Task<JokeSearchResponse> SearchAsync(
string term, CancellationToken cancellationToken);
Task<Joke> GetJokeByIdAsync(
string id, CancellationToken cancellationToken);
Task<Joke> GetRandomJokeAsync(CancellationToken cancellationToken);
}
public class JokeSearchResponse
{
public bool Success { get; init; }
public List<Joke> Body { get; init; } = new();
}
public class Joke
{
public string Punchline { get; set; } = default!;
public string Setup { get; set; } = default!;
public string Type { get; set; } = default!;
}
The contract is created based on an API you are integrating with. My general recommendations are to develop common-purpose APIs and follow Robustness Principle and Principle of least astonishment. But it is totally fine if you want to modify and transform data contract based on your needs. Just think about it from a consumer perspective.
The bread and butter of HTTP-based integrations is HttpClient
. It contains everything you need to successfully work with HTTP abstractions.
public class DadJokesApiClient : IDadJokesApiClient
{
private readonly HttpClient httpClient;
public DadJokesApiClient(HttpClient httpClient) =>
this.httpClient = httpClient;
}
Usually, we deal with JSON over HTTP APIs, so in .NET 5 System.Net.Http.Json
namespace was added to BCL. It provides many extension methods for HttpClient
and HttpContent
that perform serialization and deserialization using System.Text.Json
. If you don’t have something complex and exotic I would suggest using System.Net.Http.Json
because it frees you from writing boilerplate code. Not only it is boring, but it also is not trivial to get it right in the most efficient and bug-free way from the get-go. I suggest you check Steves’ Gordon blog post - sending and receiving JSON using HttpClient
public async Task<Joke> GetRandomJokeAsync(CancellationToken cancellationToken)
{
var jokes = await this.httpClient.GetFromJsonAsync<JokeSearchResponse>(
ApiUrlConstants.GetRandomJoke, cancellationToken);
if (jokes is { Body.Count: 0 } or { Success: false })
{
// consider creating custom exceptions for situations like this
throw new InvalidOperationException("This API is no joke.");
}
return jokes.Body.First();
}
💡 Tip: You may want to create some centralized place to manage the endpoints URLs, like this:
public static class ApiUrlConstants
{
public const string JokeSearch = "/joke/search";
public const string GetJokeById = "/joke";
public const string GetRandomJoke = "/random/joke";
}
💡 Tip: If you need to deal with complex URIs - use Flurl. It provides fluent URL building experience.
public async Task<Joke> GetJokeByIdAsync(string id, CancellationToken cancellationToken)
{
// $"{ApiUrlConstants.GetJokeById}/{id}"
var path = ApiUrlConstants.GetJokeById.AppendPathSegment(id);
var joke = await this.httpClient.GetFromJsonAsync<Joke>(path, cancellationToken);
return joke ?? new();
}
Next, we need to specify required headers (or some other required configuration). We want to provide a flexible mechanism for configuring HttpClient
used as part of SDK. In this case, we need to supply credentials in the custom header and specify a well-known “Accept” header.
💡 Tip: Expose high-level building blocks as HttpClientExtensions
. It makes it easy to discover API specific configuration. For example, if you have a custom authorization mechanism it should be supported by SDK (at least provide documentation for it).
public static class HttpClientExtensions
{
public static HttpClient AddDadJokesHeaders(
this HttpClient httpClient, string host, string apiKey)
{
var headers = httpClient.DefaultRequestHeaders;
headers.Add(ApiConstants.HostHeader, new Uri(host).Host);
headers.Add(ApiConstants.ApiKeyHeader, apiKey);
return httpClient;
}
}
Client Lifetime
To construct DadJokesApiClient
we need to create HttpClient
. As you know, HttpClient
implements IDisposable
because there is an underlying unmanageable resource - TCP connection. There is a limited amount of concurrent TCP connections that can be opened simultaneously on a single machine. So, it brings an important question - “Should I create HttpClient every time I need it or only once during an application startup?”
HttpClient
is actually a shared object. This means that under the covers it is reentrant and thread-safe. Instead of creating a new instance of HttpClient
for each execution, you should share a single instance of HttpClient
. However, this comes with its own set of issues. For example, the client will keep connections open for the lifespan of the application, it won’t respect the DNS TTL settings and it will never get DNS updates. So this isn’t a perfect solution either.
Basically, you need to manage a pool of TCP connections that are disposed from time to time to respect DNS updates. This is exactly what HttpClientFactory
does. The official documentation describes HttpClientFactory
as being “an opinionated factory for creating HttpClient instances to be used in your applications”. We will see how to use it in a moment.
Each time you get an HttpClient
object from the IHttpClientFactory
, a new instance is returned. But each HttpClient
uses an HttpMessageHandler
that’s pooled and reused by the IHttpClientFactory
to reduce resource consumption. Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes. HttpMessageHandler
has a limited lifetime.
Down below you can see how HttpClientFactory
comes into play when using HttpClient
managed by DI.
Consuming API Clients
The very basic scenario is a console application without a dependency injection container. The goal here is to give consumers the fastest way possible to integrate.
💡 Create static factory method that creates an API Client.
public static class DadJokesApiClientFactory
{
public static IDadJokesApiClient Create(string host, string apiKey)
{
var httpClient = new HttpClient()
{
BaseAddress = new Uri(host);
}
ConfigureHttpClient(httpClient, host, apiKey);
return new DadJokesApiClient(httpClient);
}
internal static void ConfigureHttpClient(
HttpClient httpClient, string host, string apiKey)
{
ConfigureHttpClientCore(httpClient);
httpClient.AddDadJokesHeaders(host, apiKey);
}
internal static void ConfigureHttpClientCore(HttpClient httpClient)
{
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new("application/json"));
}
}
Finally, we can use IDadJokesApiClient
from the console application:
var host = "https://dad-jokes.p.rapidapi.com";
var apiKey = "<token>";
var client = DadJokesApiClientFactory.Create(host, apiKey);
var joke = await client.GetRandomJokeAsync();
Console.WriteLine($"{joke.Setup} {joke.Punchline}");
Consuming API Clients. HttpClientFactory
The next step is to configure HttpClient
as part of a Dependency Injection container. I will not go into details, there is a lot of good stuff on the internet. Once again, there is a really good article from Steve’s Gordon - HttpClientFactory in ASP.NET Core
To add pooled HttpClient
to DI you need to use IServiceCollection.AddHttpClient
from Microsoft.Extensions.Http
.
💡 Provide a custom extension method to add typed HttpClient
in DI.
public static class ServiceCollectionExtensions
{
public static IHttpClientBuilder AddDadJokesApiClient(
this IServiceCollection services,
Action<HttpClient> configureClient) =>
services.AddHttpClient<IDadJokesApiClient, DadJokesApiClient>((httpClient) =>
{
DadJokesApiClientFactory.ConfigureHttpClientCore(httpClient);
configureClient(httpClient);
});
}
Use extension method like the following:
var host = "https://da-jokes.p.rapidapi.com";
var apiKey = "<token>";
var services = new ServiceCollection();
services.AddDadJokesApiClient(httpClient =>
{
httpClient.BaseAddress = new(host);
httpClient.AddDadJokesHeaders(host, apiKey);
});
var provider = services.BuildServiceProvider();
var client = provider.GetRequiredService<IDadJokesApiClient>();
var joke = await client.GetRandomJokeAsync();
logger.Information($"{joke.Setup} {joke.Punchline}");
As you see, you can use IHttpClientFactory
outside of ASP.NET Core. For example, console applications, workers, lambdas, etc.
Let’s see it running:
The interesting part here is that clients created by DI automatically logs outgoing requests. It makes development and troubleshooting so much easier.
{SourceContext}[{EventId}] // pattern
System.Net.Http.HttpClient.IDadJokesApiClient.LogicalHandler [{ Id: 100, Name: "RequestPipelineStart" }]
System.Net.Http.HttpClient.IDadJokesApiClient.ClientHandler [{ Id: 100, Name: "RequestStart" }]
System.Net.Http.HttpClient.IDadJokesApiClient.ClientHandler [{ Id: 101, Name: "RequestEnd" }]
System.Net.Http.HttpClient.IDadJokesApiClient.LogicalHandler [{ Id: 101, Name: "RequestPipelineEnd" }]
The most common scenario is web applications. Here is .NET 6 MinimalAPI example:
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var configuration = builder.Configuration;
var host = configuration["DadJokesClient:host"];
services.AddDadJokesApiClient(httpClient =>
{
httpClient.BaseAddress = new(host);
httpClient.AddDadJokesHeaders(host, configuration["DADJOKES_TOKEN"]);
});
var app = builder.Build();
app.MapGet("/", async (IDadJokesApiClient client) => await client.GetRandomJokeAsync());
app.Run();
{
"punchline": "They are all paid actors anyway",
"setup": "We really shouldn't care what people at the Oscars say",
"type": "actor"
}
Extending HTTP Client SDKs. Adding cross-cutting concerns via DelegatingHandler
HttpClient
provide an extension point - message handler. A message handler is a class that receives an HTTP request and returns an HTTP response. A wide variety of problems could be expressed as cross-cutting concerns. For example, logging, authentication, caching, header forwarding, auditing, etc. Aspect-oriented programming aims to encapsulate cross-cutting concerns into aspects to retain modularity. Typically, a series of message handlers are chained together. The first handler receives an HTTP request, does some processing, and gives the request to the next handler. At some point, the response is created and goes back up the chain.
// supports the most common requirements for most applications
public abstract class HttpMessageHandler : IDisposable
{}
// plug a handler into a handler chain
public abstract class DelegatingHandler : HttpMessageHandler
{}
Task. Assume you need to copy a list of headers from ASP.NET Core HttpContext
and pass them to all outgoing requests made by Dad Jokes API client.
public class HeaderPropagationMessageHandler : DelegatingHandler
{
private readonly HeaderPropagationOptions options;
private readonly IHttpContextAccessor contextAccessor;
public HeaderPropagationMessageHandler(
HeaderPropagationOptions options,
IHttpContextAccessor contextAccessor)
{
this.options = options;
this.contextAccessor = contextAccessor;
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (this.contextAccessor.HttpContext != null)
{
foreach (var headerName in this.options.HeaderNames)
{
var headerValue = this.contextAccessor
.HttpContext.Request.Headers[headerName];
request.Headers.TryAddWithoutValidation(
headerName, (string[])headerValue);
}
}
return base.SendAsync(request, cancellationToken);
}
}
public class HeaderPropagationOptions
{
public IList<string> HeaderNames { get; set; } = new List<string>();
}
Now, we want to “plug” DelegatingHandler
into HttpClient
request pipeline.
For non-IHttpClientFactory
scenarios, we want clients to specify a list of DelegatingHandler
so we can build an underlying chain for HttpClient
.
//DadJokesApiClientFactory.cs
public static IDadJokesApiClient Create(
string host,
string apiKey,
params DelegatingHandler[] handlers)
{
var httpClient = new HttpClient();
if (handlers.Length > 0)
{
_ = handlers.Aggregate((a, b) =>
{
a.InnerHandler = b;
return b;
});
httpClient = new(handlers[0]);
}
httpClient.BaseAddress = new Uri(host);
ConfigureHttpClient(httpClient, host, apiKey);
return new DadJokesApiClient(httpClient);
}
So, without DI container, extended DadJokesApiClient
could be constructed like this:
var loggingHandler = new LoggingMessageHandler(); //outermost
var authHandler = new AuthMessageHandler();
var propagationHandler = new HeaderPropagationMessageHandler();
var primaryHandler = new HttpClientHandler(); // the default handler used by HttpClient
DadJokesApiClientFactory.Create(
host, apiKey,
loggingHandler, authHandler, propagationHandler, primaryHandler);
// LoggingMessageHandler ➝ AuthMessageHandler ➝ HeaderPropagationMessageHandler ➝ HttpClientHandler
For DI container scenarios, on another hand, we want to provide auxiliary extension method to easily plug HeaderPropagationMessageHandler
by using IHttpClientBuilder.AddHttpMessageHandler
.
public static class HeaderPropagationExtensions
{
public static IHttpClientBuilder AddHeaderPropagation(
this IHttpClientBuilder builder,
Action<HeaderPropagationOptions> configure)
{
builder.Services.Configure(configure);
builder.AddHttpMessageHandler((sp) =>
{
return new HeaderPropagationMessageHandler(
sp.GetRequiredService<IOptions<HeaderPropagationOptions>>().Value,
sp.GetRequiredService<IHttpContextAccessor>());
});
return builder;
}
}
Here is how extended MinimalAPI example looks like:
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var configuration = builder.Configuration;
var host = configuration["DadJokesClient:host"];
services.AddDadJokesApiClient(httpClient =>
{
httpClient.BaseAddress = new(host);
httpClient.AddDadJokesHeaders(host, configuration["DADJOKES_TOKEN"]);
}).AddHeaderPropagation(o => o.HeaderNames.Add("X-Correlation-ID"));
var app = builder.Build();
app.MapGet("/", async (IDadJokesApiClient client) => await client.GetRandomJokeAsync());
app.Run();
💡 Sometimes functionality like this is reused by other services. You might want to take it one step further and factor out all shared code into a common NuGet package and use it in HTTP Client SDKs.
Third-Party Extensions
Not only we can write our own message handlers. There is a lot of useful NuGet packages provided and supported by .NET OSS community. Here are my favorites:
💡 Resiliency patterns - retry, cache, fallback, etc.: Very often, in distrusted systems world you need to ensure high availability by incorporating some resilience policies. Luckily, we have a built-in solution to build and define policies in .NET - Polly. There is out-of-the-box integration with IHttpClientFactory
provided by Polly. This uses a convenience method, IHttpClientBuilder.AddTransientHttpErrorPolicy. It configures a policy to handle errors typical of HTTP calls: HttpRequestException
, HTTP 5XX status codes (server errors), HTTP 408 status code (request timeout).
services.AddDadJokesApiClient(httpClient =>
{
httpClient.BaseAddress = new(host);
}).AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10)
}));
For example, transient errors might be handled proactively by using Retry and Circuit Breaker patterns. Usually, we use a retry pattern when there is a hope that downstream service will self-correct eventually. Waiting between retries provides an opportunity for a downstream service to stabilize. It is common to use retries based on the Exponential Backoff algorithm. On paper, it sounds great, but in real-world scenarios, the retry pattern may be overused. Additional retries might be the source of additional load or spikes. In the worst case, resources in the caller may then become exhausted or excessively blocked, waiting for replies which will never come causing an upstream-cascading failure.
This is when the Circuit Breaker pattern comes into play. It detects the level of faults and prevents calls to a downstream service when a fault threshold is exceeded. Use this pattern when there is no chance of succeeding - for example, where a subsystem is completely offline or struggling under load. The idea behind Circuit Breaker is pretty straightforward, although, you might build something more complex on top of it. When faults exceed the threshold, calls are placed through the circuit, so instead of processing a request, we practice the fail-fast approach, throwing an exception immediately.
Polly is really powerful and it provides a way to combine resilience strategies. See PolicyWrap.
Here is a classification of the strategies you might want to use:
Designing reliable systems could be a challenging task, I suggest you investigate the subject on your own. Here is a good introduction - .NET microservices - Architecture e-book: Implement resilient applications
💡 Authentication in OAuth2/OIDC: If you need to manage user and client access tokens I suggest using IdentityModel.AspNetCore. It acquires, caches, and rotates tokens for you, see the docs.
// adds user and client access token management
services.AddAccessTokenManagement(options =>
{
options.Client.Clients.Add("identity-provider", new ClientCredentialsTokenRequest
{
Address = "https://demo.identityserver.io/connect/token",
ClientId = "my-awesome-service",
ClientSecret = "secret",
Scope = "api" // optional
});
});
// registers HTTP client that uses the managed client access token
// adds the access token handler to HTTP client registration
services.AddDadJokesApiClient(httpClient =>
{
httpClient.BaseAddress = new(host);
}).AddClientAccessTokenHandler();
Testing HTTP Client SDKs
By this time, you should be pretty comfortable designing and writing your own HTTP Client SDKs. The only thing that is left is to write some tests to ensure expected behavior. Note, it might be a good idea to skip extensive unit testing and write more integration or e2e to ensure proper integration. For now, I will show you how to unit test DadJokesApiClient
.
As you have seen previously, HttpClient
is extensible. Furthermore, we can replace the standard HttpMessageHandler
with the test version. So, instead of sending actual requests over the wire, we will use the mock. This technique opens tons of opportunities because we can simulate all kinds of behaviors of HttpClient
that otherwise could be hard to replicate in a normal situation.
Let’s define reusable methods to create a mock of HttpClient
that we will pass as a dependency to DadJokesApiClient
.
public static class TestHarness
{
public static Mock<HttpMessageHandler> CreateMessageHandlerWithResult<T>(
T result, HttpStatusCode code = HttpStatusCode.OK)
{
var messageHandler = new Mock<HttpMessageHandler>();
messageHandler.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = code,
Content = new StringContent(JsonSerializer.Serialize(result)),
});
return messageHandler;
}
public static HttpClient CreateHttpClientWithResult<T>(
T result, HttpStatusCode code = HttpStatusCode.OK)
{
var httpClient = new HttpClient(CreateMessageHandlerWithResult(result, code).Object)
{
BaseAddress = new("https://api-client-under-test.com"),
};
return httpClient;
}
}
From this point, unit testing is a pretty simple process:
public class DadJokesApiClientTests
{
[Theory, AutoData]
public async Task GetRandomJokeAsync_SingleJokeInResult_Returned(Joke joke)
{
// Arrange
var response = new JokeSearchResponse
{
Success = true,
Body = new() { joke }
};
var httpClient = CreateHttpClientWithResult(response);
var sut = new DadJokesApiClient(httpClient);
// Act
var result = await sut.GetRandomJokeAsync();
// Assert
result.Should().BeEquivalentTo(joke);
}
[Fact]
public async Task GetRandomJokeAsync_UnsuccessfulJokeResult_ExceptionThrown()
{
// Arrange
var response = new JokeSearchResponse();
var httpClient = CreateHttpClientWithResult(response);
var sut = new DadJokesApiClient(httpClient);
// Act
// Assert
await FluentActions.Invoking(() => sut.GetRandomJokeAsync())
.Should().ThrowAsync<InvalidOperationException>();
}
}
Using HttpClient
is the most flexible approach. You have full control over integration with APIs. But, there is a downside, you need to write a lot of boilerplate code. In some situations, an API you are integrating with is trivial so you don’t really need all capabilities provided by HttpClient
, HttpRequestMessage
, HttpResponseMessage
.
Pros ➕:
- Full control over behavior and data contracts. You can even write “smart” API Client and move some logic inside SDK if it makes sense for a particular scenario. For example, you can throw custom exceptions, transform requests and responses, provide default values for headers, etc.
- Full control over serialization and deserialization process
- Easy to debug and troubleshoot. A stack trace is simple and you can always spin up the debugger to see what is happening under the hood.
Cons ➖:
- Need to write a lot of repetitive code
- Someone should maintain a code base in case of API changes and bugs. This is a tedious and error-prone process.
Writing HTTP Client SDK. Declarative approach
The less code, the fewer bugs.
Refit is an automatic type-safe REST library for .NET. It turns your REST API into a live interface. Refit uses System.Text.Json
as the default JSON serializer.
Every method must have an HTTP attribute that provides the request method and relative URL.
using Refit;
public interface IDadJokesApiClient
{
/// <summary>
/// Searches jokes by term.
/// </summary>
[Get("/joke/search")]
Task<JokeSearchResponse> SearchAsync(
string term,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets a joke by id.
/// </summary>
[Get("/joke/{id}")]
Task<Joke> GetJokeByIdAsync(
string id,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets a random joke.
/// </summary>
[Get("/random/joke")]
Task<JokeSearchResponse> GetRandomJokeAsync(
CancellationToken cancellationToken = default);
}
Refit generates type that implements IDadJokesApiClient
based on information provided by Refit.HttpMethodAttribute
Consuming API Clients. Refit
The approach is the same as for vanilla HttpClient
integration, but instead of constructing a client manually, we use static method provided by Refit.
public static class DadJokesApiClientFactory
{
public static IDadJokesApiClient Create(
HttpClient httpClient,
string host,
string apiKey)
{
httpClient.BaseAddress = new Uri(host);
ConfigureHttpClient(httpClient, host, apiKey);
return RestService.For<IDadJokesApiClient>(httpClient);
}
// ...
}
For DI container scenarios, we can use Refit.HttpClientFactoryExtensions.AddRefitClient
extension method.
public static class ServiceCollectionExtensions
{
public static IHttpClientBuilder AddDadJokesApiClient(
this IServiceCollection services,
Action<HttpClient> configureClient)
{
var settings = new RefitSettings()
{
ContentSerializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
WriteIndented = true,
})
};
return services.AddRefitClient<IDadJokesApiClient>(settings)
.ConfigureHttpClient((httpClient) =>
{
DadJokesApiClientFactory.ConfigureHttpClient(httpClient);
configureClient(httpClient);
});
}
}
Usage:
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateBootstrapLogger();
builder.Host.UseSerilog((ctx, cfg) => cfg.WriteTo.Console());
var services = builder.Services;
services.AddDadJokesApiClient(httpClient =>
{
var host = configuration["DadJokesClient:host"];
httpClient.BaseAddress = new(host);
httpClient.AddDadJokesHeaders(host, configuration["DADJOKES_TOKEN"]);
});
var app = builder.Build();
app.MapGet("/", async Task<Joke> (IDadJokesApiClient client) =>
{
var jokeResponse = await client.GetRandomJokeAsync();
return jokeResponse.Body.First(); // unwraps JokeSearchResponse
});
app.Run();
Note, since the contract of the generated client should match the underlying data contract, we no longer have control of contract transformation and this responsibility is delegated to consumers.
Let’s see how the code above works in practice. The output of MinimalAPI example is different because I’ve added Serilog logging.
{
"punchline": "Forgery.",
"setup": "Why was the blacksmith charged with?",
"type": "forgery"
}
As usual, there are some pros and some cons:
Pros ➕:
- Easy to use and develop API clients.
- Highly configurable. Flexible enough to get things done.
- No need for additional unit testing
Cons ➖:
- Hard to troubleshoot. Sometimes it can be hard to understand how the generated code works. For example, there is a mismatch in configuration.
- Requires other team members to understand how to read and write code developed with Refit.
- Still consumes some time for medium/large APIs.
Honorable mentions: RestEase, RESTFulSense
Writing HTTP Client SDK. Automated approach
There is a way to fully automate HTTP Client SDKs. The OpenAPI/Swagger specification uses JSON and JSON Schema to describe a RESTful web API. The NSwag project provides tools to generate client code from these OpenAPI specifications. Everything can be automated via CLI (distributed via NuGet tool or build target; or NPM).
Actually, Dad Jokes API doesn’t provide OpenAPI, so I had to write it manually. Fortunately, it was quite easy to do:
openapi: '3.0.2'
info:
title: Dad Jokes API
version: '1.0'
servers:
- url: https://dad-jokes.p.rapidapi.com
paths:
/joke/{id}:
get:
description: ''
operationId: 'GetJokeById'
parameters:
- name: "id"
in: "path"
description: ""
required: true
schema:
type: "string"
responses:
'200':
description: successful operation
content:
application/json:
schema:
"$ref": "#/components/schemas/Joke"
/random/joke:
get:
description: ''
operationId: 'GetRandomJoke'
parameters: []
responses:
'200':
description: successful operation
content:
application/json:
schema:
"$ref": "#/components/schemas/JokeResponse"
/joke/search:
get:
description: ''
operationId: 'SearchJoke'
parameters: []
responses:
'200':
description: successful operation
content:
application/json:
schema:
"$ref": "#/components/schemas/JokeResponse"
components:
schemas:
Joke:
type: object
required:
- _id
- punchline
- setup
- type
properties:
_id:
type: string
type:
type: string
setup:
type: string
punchline:
type: string
JokeResponse:
type: object
properties:
sucess:
type: boolean
body:
type: array
items:
$ref: '#/components/schemas/Joke'
Now, we want to generate HTTP Client SDK automatically. Let’s use NSwagStudio.
Here is how the generated IDadJokesApiClient
looks like (XML comments are deleted for brevity):
[System.CodeDom.Compiler.GeneratedCode("NSwag", "<IP_ADDRESS> (NJsonSchema v<IP_ADDRESS> (Newtonsoft.Json v<IP_ADDRESS>))")]
public partial interface IDadJokesApiClient
{
System.Threading.Tasks.Task<Joke> GetJokeByIdAsync(string id);
System.Threading.Tasks.Task<Joke> GetJokeByIdAsync(string id, System.Threading.CancellationToken cancellationToken);
System.Threading.Tasks.Task<JokeResponse> GetRandomJokeAsync();
System.Threading.Tasks.Task<JokeResponse> GetRandomJokeAsync(System.Threading.CancellationToken cancellationToken);
System.Threading.Tasks.Task<JokeResponse> SearchJokeAsync();
System.Threading.Tasks.Task<JokeResponse> SearchJokeAsync(System.Threading.CancellationToken cancellationToken);
}
As usual, we want to provide the registration of typed client as an extension method:
public static class ServiceCollectionExtensions
{
public static IHttpClientBuilder AddDadJokesApiClient(
this IServiceCollection services, Action<HttpClient> configureClient) =>
services.AddHttpClient<IDadJokesApiClient, DadJokesApiClient>(
httpClient => configureClient(httpClient));
}
Usage:
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
var services = builder.Services;
services.AddDadJokesApiClient(httpClient =>
{
var host = configuration["DadJokesClient:host"];
httpClient.BaseAddress = new(host);
httpClient.AddDadJokesHeaders(host, configuration["DADJOKES_TOKEN"]);
});
var app = builder.Build();
app.MapGet("/", async Task<Joke> (IDadJokesApiClient client) =>
{
var jokeResponse = await client.GetRandomJokeAsync();
return jokeResponse.Body.First();
});
app.Run();
Let’s run it and enjoy the last joke of this article:
{
"punchline": "And it's really taken off",
"setup": "So I invested in a hot air balloon company...",
"type": "air"
}
Pros ➕:
- Based on the well-known specification
- Supported by rich set of tools and vibrant community
- Fully automated, new SDK can be generated as part of CI/CD process every time OpenAPI specification is changed
- Generate SDKs for multiple programming languages
- Relatively easy to troubleshoot since we can see the code generated by the toolchain.
Cons ➖:
- Can’t be applied without proper OpenAPI specification
- Hard to customize and control the contract of generated API Client
Honorable mentions: AutoRest, Visual Studio Connected Services
Choosing the right approach
We have three different ways of producing SDK clients. The selection process can be simplified to the next categories
I’m a simple man/woman/non-binary, I want to have full control over my HTTP Client integration.
Use manual approach.
I’m a busy man/woman/non-binary, but I still want to have somewhat control.
Use a declarative approach.
I’m a lazy man/woman/non-binary. Do the thing for me.
Use an automated approach.
Decision chart:
Summary
We’ve reviewed different ways of developing HTTP Client SDKs. Choosing the right approach depends on use-case and requirements, but I hope this article gives you nice foundations for making the best design decisions. Thank you.
Reference
- https://github.com/NikiforovAll/http-sdk-guide
- https://www.stevejgordon.co.uk/sending-and-receiving-json-using-httpclient-with-system-net-http-json
- https://devblogs.microsoft.com/dotnet/net-5-new-networking-improvements/
- https://www.stevejgordon.co.uk/httpclientfactory-aspnetcore-outgoing-request-middleware-pipeline-delegatinghandlers
- https://www.aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
- https://www.stevejgordon.co.uk/httpclient-creation-and-disposal-internals-should-i-dispose-of-httpclient
- https://www.assemblyai.com/blog/getting-started-with-httpclientfactory-in-c-sharp-and-net-5/
- https://www.stevejgordon.co.uk/httpclientfactory-named-typed-clients-aspnetcore
- https://andrewlock.net/understanding-scopes-with-ihttpclientfactory-message-handlers/
- https://andrewlock.net/exporing-the-code-behind-ihttpclientfactory/
- https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/
- https://app.pluralsight.com/library/courses/using-httpclient-consume-apis-dot-net


SMP is a company, established under a mission of service to humanity, we provide a wide range of pharmaceuticals and we are building a reputation of quality and integrity known around the world. The products that we supply at SMP Meets the best safety and quality standards. We are being audited regularly by officials to ensure that the highest safety and quality criteria were met.


AeroGear Xamarin Auth SDK
Mobile authentication SDK based on Keycloak using OpenID Connect.
Provides authentication features like access control and two factor authentication through Keycloak.
Usage
To use the Auth SDK you’ll first need to:
-
Have a Keycloak instance. See Setting up Keycloak.
-
Import the Core module. See the Core documentation.
Setting up Keycloak
Using OpenShift
-
If you do not have mobile services enabled in your openshift cluster follow this Local Setup guide.
-
Navigate to your Openshift cluster and in the Service Catalog search for the Keycloak service.
-
Click on the Keycloak service and you will be prompted to fill in details about your app. For now you can leave these as they are. Navigate through the setup and click Create. This will provision the Keycloak service in the project you specified and create a public Client to be used with an app along with a bearer-only client. See Keycloak OIDC client documentation.
After provisioning, the Keycloak service will be available at the exposed Route. You can view this route inside your project or use the below command to get the route:
oc get route keycloak --template "http://{{.spec.host}} "
The route should look like https://keycloak-myproject.<IP_ADDRESS>.nip.io/auth/
.
Standalone
To setup standalone Keycloak follow Keycloak’s guide here.
Configuration file
A mobile-services.json
file must exist in Resources folder of the apps directory. It should specify configuration
for Keycloak. This configuration can be generated by the AeroGear Mobile CLI.
For an example of Keycloak configuration see example apps mobile-services.json.
The Auth SDK will use this configuration to communicate with Keycloak.
Initializing the SDK
Before using the Auth SDK it must first be initialized and configured. During
configuration an AuthenticationConfig
must be provided to the SDK.
An example of initializing and configuring the SDK is:
Android
var app = new App();
MobileCoreAndroid.Init(app.GetType().Assembly,ApplicationContext);
var authService = AuthService.InitializeService();
var authConfig = AuthenticationConfig.Builder.RedirectUri("org.aerogear.mobile.example:/callback").Build();
authService.Configure(authConfig);
For Android an intent-filter
should be configured with the callback URL specified in AuthenticateOptions in
the apps AndroidManifest.xml . See the example app.
|
Retrieving the current user
// auth service is already initialised
var user = MobileCore.Instance.GetInstance<IAuthService>().CurrentUser();
var username = user.Username;
var email = user.Email;
var accessToken = user.AccessToken;
Authenticating
IAuthService service = MobileCore.Instance.GetInstance<IAuthService>();
var authOptions = DependencyService.Get<IAuthenticateOptionsProvider>().GetOptions();
service.Authenticate(authOptions).ContinueWith(result =>
{
// load some view
});
Retrieving a User’s Roles
// listing all Roles for a previously initialised authService
var user = MobileCore.Instance.GetInstance<IAuthService>().CurrentUser();
var roleItems = new List<string> { };
foreach(var role in user.getRoles())
{
roleItems.Add(role.ToString());
}
// do something with roleItems;
// checking if the current user has a specific realm role
var user = MobileCore.Instance.GetInstance<IAuthService>().CurrentUser();
bool isAdmin = user.HasRealmRole("user_admin");
if (isAdmin) {
// Show some component
}
// checking if the user has a role from a specific resource named my_resource.
bool isModerator = user.HasResourceRole("my_resource", "user_moderator");
if (isModerator) {
// Enable some button
}
Logging Out
// auth service already initialised
var authService = MobileCore.Instance.GetInstance<IAuthService>();
authService.Logout(authService.CurrentUser()).ContinueWith(result =>
{
// load some view
});
| To perform backchannel or federated logouts, you must enable the Backchannel Logout option for the federated identity provider. More information is available in the Keycloak documentation under OIDC Identity Providers. |