CTRL+. and this terrific online namespace mapping tool, the learning curve is not too steep.A noteworthy, radical change, refers to the way application settings are managed. If you are used to doing
ConfigurationManager.AppSettings["MySettingKey"], then you are out of luck. Doing that directly in your Controllers was a bad idea anyway and you probably should have been accessing those settings via an abstraction. Nevertheless, the new paradigm brings in the following advantages:- JSON format instead of XML. Nested settings are supported
- No more magic strings, configuration objects are now strongly typed POCO's
- Dependency Injection support right out of the box

There is a bit more initial configuration but the end result is cleaner and more consistent with C# best practices. I will attempt to illustrate the process with a practical example used at Planet Diego.
Example: A list of image files is retrieved from an Amazon AWS S3 bucket. The order of the files within the list is randomized and a specific quantity is extracted (dictated by an application setting). The resulting list is rendered on a View as part of a JSON structure to be consumed by a client-side script that initiates a slide image transition between the image files in the JSON array.
Lets begin with
appsettings.json, this is the new file in which application settings are stored, no more Web.config or App.config.{
  "AppSettings": {
    "GoogleTrackingId": "your-google-analytics-tracking-id",
    "EmailSender": "no-reply@yourdomain.com",
    "Captcha": {
      "SiteKey": "your-reCAPTCHA-site-key",
      "SecretKey": "your-reCAPTCHA-secret-key"
    },
    "CDNDomain": "cdn.planetdiego.com",
    "HomeGalleryKey": "web/home/",
    "HomeImageDefault": "img/person-img.jpg",
    "HomeImageCacheSeconds": 180.0,
    "HomeImageRotateSeconds": 24,
    "HomeImageMaxPicks": 30
  },
  "AWS": {
    "S3": {
      "BucketName": "dot-blog"
    },
    "ProfileName": "development",
    "Region": "us-east-1",
    "AccessKey": "your-aws-access-key",
    "SecretKey": "your-aws-secret-key"
  },
  "ApplicationInsights": {
    "InstrumentationKey": "your-instrumentation-key"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}
appsettings.Production.json. Each configured environment can have its own specific overrides by using this convention. To understand how this works, see the contents of the Startup.cs file, especially line 6 in which the optional settings file containing the overrides and/or additions is loaded.public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables();
    if (env.IsDevelopment())
    {
        // This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
        builder.AddApplicationInsightsSettings(developerMode: true);
    }
    Configuration = builder.Build();
}
public class AppSettings
{
    public string GoogleTrackingId { get; set; }
    public string EmailRecipient { get; set; }
    public Captcha Captcha { get; set; }
    public string CDNDomain { get; set; }
    public string HomeGalleryKey { get; set; }
    public string HomeImageDefault { get; set; }
    public double HomeImageCacheSeconds { get; set; }
    public double HomeImageRotateSeconds { get; set; }
    public int HomeImageMaxPicks { get; set; }
}
public class Captcha
{
    public string SiteKey { get; set; }
    public string SecretKey { get; set; }
}
public class AWS
{
    public string ProfileName { get; set; }
    public string Region { get; set; }
    public string AccessKey { get; set; }
    public string SecretKey { get; set; }
    public S3 S3 { get; set; }
}
public class S3
{
    public string BucketName { get; set; }
}
Startup.cs look for the ConfigureServices method. Take a look at lines 4 & 5 in which we map our POCO's to the sections in the JSON file. That is really all that is needed, now those dependencies will be injected wherever we need them.// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
    services.Configure<AWS>(Configuration.GetSection("AWS"));
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddTransient<IStorageService, S3StorageService>();
    services.AddMvc();
    // Add any other application services here...
}
IOptions<AppSettings>. The IOptions interface is part of the Microsoft.Extensions.Options namespace. In order to access its contents, use the Value property on the instance. Furthermore, the instance is essentially a singleton which is passed throughout the lifetime of the application. Let's review how this works with a practical example in which the AWS section of the AppSettings is injected into the class constructor.public interface IStorageService
{
    Task<IList<string>> List(string startingKey = "", int maxKeys = 100, bool hasEmptyObjects = false);
}
public class S3StorageService : IStorageService
{
    private readonly AWS _settings;
    private readonly RegionEndpoint _region;
    public S3StorageService(IOptions<AWS> settings)
    {
        _settings = settings.Value;
        _region = RegionEndpoint.GetBySystemName(_settings.Region);
    }
    public async Task<IList<string>> List(string startingKey = "", int maxKeys = 100, bool hasEmptyObjects = false)
    {
        var result = new List<string>();
        using (var client = new AmazonS3Client(
                _settings.AccessKey,
                _settings.SecretKey,
                _region))
        {
            try
            {
                ListObjectsV2Request request = new ListObjectsV2Request
                {
                    BucketName = _settings.S3.BucketName,
                    MaxKeys = maxKeys,
                    Prefix = startingKey
                };
                ListObjectsV2Response response;
                do
                {
                    response = await client.ListObjectsV2Async(request);
                    // Process response.
                    foreach (S3Object entry in response.S3Objects)
                    {
                        if (entry.Size > 0 || hasEmptyObjects)
                            result.Add(entry.Key);
                    }
                    Debug.WriteLine("Next Continuation Token: {0}", response.NextContinuationToken);
                    request.ContinuationToken = response.NextContinuationToken;
                } while (response.IsTruncated == true);
            }
            catch (AmazonS3Exception amazonS3Exception)
            {
                if (amazonS3Exception.ErrorCode != null &&
                    (amazonS3Exception.ErrorCode.Equals("InvalidAccessKeyId")
                    ||
                    amazonS3Exception.ErrorCode.Equals("InvalidSecurity")))
                {
                    Debug.WriteLine("Check the provided AWS Credentials.");
                }
                else
                {
                    Debug.WriteLine("Error occurred. Message:'{0}' when listing objects",
                     amazonS3Exception.Message);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }
        return result;
    }
}
List method returns a promise for a list of files in the given S3 bucket and starting key. Make sure you install the AWSSDK.S3 Nuget package to work with Amazon S3 and be able to use the code above.The same applies to the
Home controller. Below we inject the AppSettings section and the S3StorageService class via its implemented interface (see line 7 as part of the ConfigureServices method). To spice things up we randomize the order of the returned files using an extension method and then grab up to the maximum number allowed specified on the settings value HomeImageMaxPicks.public class HomeController : Controller
{
    private readonly AppSettings _appSettings;
    private readonly IStorageService _storage;
    public HomeController(
        IOptions<AppSettings> appSettings,
        IStorageService storage
        )
    {
        _appSettings = appSettings.Value;
        _storage = storage;
    }
    public async Task<IActionResult> Index()
    {
        var vm = new HomeViewModel();
        vm.MainImageDefault = _appSettings.HomeImageDefault;
        vm.MainImageRotateEvery = _appSettings.HomeImageRotateSeconds;
        vm.MainImageBase = string.Concat("//", _appSettings.CDNDomain, "/");
        var images = await _storage.List(_appSettings.HomeGalleryKey);
        if (images.Any())
        {
            vm.MainImageKeys = images.Randomize().Take(_appSettings.HomeImageMaxPicks).ToList();
        }
        return View(vm);
    }
}
public class HomeViewModel
{
    public string MainImageDefault { get; set; }
    public string MainImageBase { get; set; }
    public IList<string> MainImageKeys { get; set; }
    public double MainImageRotateEvery { get; set; }
    public HomeViewModel()
    {
        MainImageKeys = new List<string>();
    }
}
public static class EnumerableExtensions
{
    private static Random _rnd = new Random();
    public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
    {
        return source.OrderBy<T, int>((item) => _rnd.Next());
    }
}
_ViewImports.cshtml, in the Views folder which allows you to add namespaces that could be accessed by all views. However, it can also be used to perform dependency injection using the keyword inject.@using MyApp.Web;
@using MyApp.ViewModels;
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@inject Microsoft.Extensions.Options.IOptions<MyApp.Models.AppSettings> AppSettings
AppSettings which gives us access to the class on all views.Now, on the view itself, we will be mapping some settings and parts of the ViewModel to Javascript variables so they can be easily consumed client-side. The code below is a Partial View invoked from the Home/Index View with
@{Html.RenderPartial("_JsInit", Model);}.@model HomeViewModel
<script type="text/javascript">
    // Global Application Namespace:
    var myApp = myApp || {};
    myApp = {
        app: {
            siteRoot: '@Url.Content("~")',
            cdn: '@AppSettings.Value.CDNDomain',
            gaId: '@AppSettings.Value.GoogleTrackingId'
        },
        widgets: {
            reCaptchaSiteKey: '@AppSettings.Value.Captcha.SiteKey'
        },
        mainImages: {
            fallback: '@Model.MainImageDefault',
            rotateEvery: @Model.MainImageRotateEvery,
            base: '@Model.MainImageBase',
            keys: @Html.Raw(Json.Serialize(Model.MainImageKeys))
        }
    };
</script>
 
 
No comments: