Skip to content
This repository has been archived by the owner on Nov 28, 2019. It is now read-only.

Commit

Permalink
ASPNET Config (#182)
Browse files Browse the repository at this point in the history
* Minor updates

* Updating sample to use configuration for quotes instead of hard-coding them.

* Updated to describe sample

* Finishing config

* remove placeholder

* Adding notes about usings and packages
  • Loading branch information
ardalis authored Dec 1, 2016
1 parent 20be2e8 commit e151a10
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,4 @@ _Pvt_Extensions

# FAKE - F# Make
.fake/
.vscode/
81 changes: 76 additions & 5 deletions content/asp.net/getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,86 @@ Download a ZIP containing this tutorial's sample files:
- [Initial Version] - Use this as a starting point when following along with the tutorial yourself
- [Completed Version] - Includes the completed versions of all samples

See the [Issue](https://github.com/dotnet/training-tutorials/issues/51) to claim this lesson and/or view what should be included in it.
## Adding Support for Configuration

## First Header
So far the Quotes app you're building hasn't needed any configuration values. However, most real apps need to store some values related to their configuration somewhere, typically in a file that's deployed with the app. ASP.NET Core has a very extensible configuration system, allowing you to choose from a variety of built-in configuration sources or to customize your own. You're not limited to a particular file or file format, and you can even add configuration values directly using code.

Start the lesson here.
Typically in ASP.NET Core apps, configuration is set up in the ``Startup`` class. An instance of the ``IConfigurationRoot`` type is created using a ``ConfigurationBuilder``. This can be done in your ``Startup`` class's constructor:

...
```c#
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("quotes.json", optional: false, reloadOnChange: true);

Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; set;}
```

In this code, the constructor uses an ``IHostingEnvironment`` instance to set the base configuration path to the content root path (the root of the project). Then, it specifies that configuration will come from a required JSON file, ``quotes.json``. The result of the ``Build`` method is stored in the ``Configuration`` property, which is accessible from elsewhere in ``Startup``.

Note that for the above code to compile, you'll need to be sure to include these namespaces:

```c#
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
```

You'll also need to reference these packages:

* Microsoft.Extensions.Configuration.Json
* Microsoft.Extensions.Options.ConfigurationExtensions


## Accessing Configuration Values as Options

ASP.NET Core uses the [options pattern](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration#options-config-objects) to access configuration values in a strongly-typed manner. Once configured, strongly typed values may be requested from any type or method that supports [dependency injection](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) (which includes most ASP.NET Core types, as well as the ``Configure`` method in ``Startup``, as you'll see in a moment). To use the pattern, simply request an instance of ``IOptions<T>`` where ``T`` is the type you're trying to access from configuration. Currently, the quotes app stores its data in a hard-coded ``List<Quotation>`` in ``QuotationStore``. To load these quotes from a configuration source, an instance of ``IOptions<List<Quotation>>`` is used. To access the strongly-typed value, use the ``Value`` property.

```c#
public void Configure(IApplicationBuilder app,
IOptions<List<Quotation>> quoteOptions)
{
// other code omitted
var quotes = quoteOptions.Value;
if(quotes != null)
{
QuotationStore.Quotations = quotes;
}
}
```

Ultimately the app can be refactored so that the ``QuotationStore`` isn't static, and can accept the configured quotes through dependency injection itself. The current design works, but isn't ideal since any code can manipulate the ``Quotations`` property, violating the principle of [encapsulation](http://deviq.com/encapsulation/).

## Setting up Options

In order for the options pattern to work, you first need to add options support in ``ConfigureServices``. Then, you need to add the options value to the services container. You can specify the value of the options directly, or you can specify that it should be loaded from configuration, as the following code demonstrates:

```c#
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<List<Quotation>>(Configuration.GetSection("Quotes"));
}
```

It's important when using a file for your configuration that you format it appropriately. In this example, the ``quotes.json`` file needs to container a ``quotes`` section, which in turn should contain a collection of ``Quotation`` instances. An example of ``quotes.json``:

```json
{
"Quotes": [
{ "author": "Ralph Johnson", "quote": "Before software can be reusable it first has to be usable." },
{ "author": "Albert Einstein", "quote": "Make everything as simple as possible, but not simpler." },
{ "author": "Dwight Eisenhower", "quote": "I have always found that plans are useless, but planning is indispensable." }
]
}
```

## Next Steps

Give the reader some additional exercises/tasks they can perform to try out what they've just learned.
Since the configuration options needed in this lesson were inside of the ``Startup`` class, the method could have directly accessed the settings from the ``Configuration`` property. Modify the code to use this approach, and consider whether you prefer it to the use of the options pattern in this instance.

Note that in most situations, such as using configuration options from controllers, middleware, or filters, your code won't have direct access to a configuration instance.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace ConsoleApplication
{
public static class QuotationStore
{
public static List<Quotation> Quotations {get; private set;}
public static List<Quotation> Quotations {get; set;}

static QuotationStore()
{
Expand Down
31 changes: 28 additions & 3 deletions content/asp.net/getting-started/samples/quotes/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,34 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace ConsoleApplication
{
public class Startup
{
public void Configure(IApplicationBuilder app)
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("quotes.json", optional: false, reloadOnChange: true);

Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; set;}

public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<List<Quotation>>(Configuration.GetSection("Quotes"));
}

public void Configure(IApplicationBuilder app,
IOptions<List<Quotation>> quoteOptions)
{
// app.UseDefaultFiles();
app.UseStaticFiles();

app.Use(async (context, next) =>
Expand All @@ -19,7 +39,12 @@ public void Configure(IApplicationBuilder app)
await next();
});

// next steps solution
var quotes = quoteOptions.Value;
if(quotes != null)
{
QuotationStore.Quotations = quotes;
}

app.Map("/quote", builder => builder.Run(async context =>
{
var id = int.Parse(context.Request.Path.ToString().Split('/')[1]);
Expand Down
71 changes: 71 additions & 0 deletions content/asp.net/getting-started/samples/quotes/quotes.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.0</TargetFramework>
<PreserveCompilationContext>true</PreserveCompilationContext>
</PropertyGroup>

<PropertyGroup>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
</PropertyGroup>

<ItemGroup>
<Compile Include="**\*.cs" Exclude="$(GlobalExclude)" />
<EmbeddedResource Include="**\*.resx" Exclude="$(GlobalExclude)" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NETCore.App">
<Version>1.0.1</Version>
</PackageReference>
<PackageReference Include="Microsoft.NET.Sdk.Web">
<Version>1.0.0-alpha-20161104-2-112</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Mvc">
<Version>1.0.1</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Razor.Tools">
<Version>1.0.0-preview2-final</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Routing">
<Version>1.0.1</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel">
<Version>1.0.1</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.StaticFiles">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.Json">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Console">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Debug">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink.Loader">
<Version>14.0.0</Version>
</PackageReference>
</ItemGroup>

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
7 changes: 7 additions & 0 deletions content/asp.net/getting-started/samples/quotes/quotes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"Quotes": [
{ "author": "Ralph Johnson", "quote": "Before software can be reusable it first has to be usable." },
{ "author": "Albert Einstein", "quote": "Make everything as simple as possible, but not simpler." },
{ "author": "Dwight Eisenhower", "quote": "I have always found that plans are useless, but planning is indispensable." }
]
}

0 comments on commit e151a10

Please sign in to comment.