Tips & Tricks: Delaying Amazon Service configuration when using .NET Dependency Injection

海外精选
海外精选的内容汇集了全球优质的亚马逊云科技相关技术内容。同时,内容中提到的“AWS” 是 “Amazon Web Services” 的缩写,在此网站不作为商标展示。
0
0
{"value":"#### **Tips & Tricks: Delaying AWS Service configuration when using .NET Dependency Injection**\nThe [AWSSDK.Extensions.NETCore.Setup](https://www.nuget.org/packages/AWSSDK.Extensions.NETCore.Setup/) package provides extensions for enabling AWS Service Client creation to work with [native .NET Dependency Injection](https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection). Bindings for one or more services can be registered via the included```ddAWSService<TService>```method and a shared configuration can be added and customized via the ```AddDefaultAWSOptions``` method.\n\nC#\n```\npublic class Startup\n{ \n public void ConfigureServices(IServiceCollection services)\n {\n // support injecting email client\n services.AddAWSService<IAmazonSimpleEmailServiceV2>();\n // customize amazon clients\n services.AddDefaultAWSOptions(new AWSOptions\n {\n Region = RegionEndpoint.USWest2\n });\n }\n}\n```\nRecently, several customers reported in the [AWS .NET SDK GitHub repository](https://github.com/aws/aws-sdk-net) that they wanted to setup Dependency Injection (DI) for AWS Services and customize their configuration. In a traditional .NET Core application the DI Container, ```IServiceCollection```, is initialized early during app startup in the Startup class. However, what if you wanted to defer initializing the ```AWSOptions``` object until later in the application lifecycle? What if you had an [ASP.NET](http://asp.net/) Core application and wanted to customize ```AWSOptions``` based on the incoming request as was requested in this [issue](https://github.com/aws/aws-aspnet-cognito-identity-provider/issues/210)?\n\nC#\n```\npublic void ConfigureServices(IServiceCollection services)\n{\n services.AddDefaultAWSOptions(new AWSOptions\n {\n // My app doesn't _yet_ have the data needed to configure AWSOptions!\n });\n}\n```\n\nWhile it’s always been technically possible to add the necessary deferred binding, doing so required a deep understanding of ```IServiceCollection```. Fortunately, [isdaniel](https://github.com/isdaniel) recently sent the [AWS .NET SDK](https://github.com/aws/aws-sdk-net/pull/1958) team a PR that [added](https://github.com/aws/aws-sdk-net/pull/1958/files#diff-07243a2e5685453e039ccbfd7b0e0260136ca42d737ca017f0c86a89ea303cadR43) an overload to the ```AddDefaultAWSOptions``` method to greatly simplify adding a deferred binding:\n\nC#\n```\npublic static class ServiceCollectionExtensions\n{\n public static IServiceCollection AddDefaultAWSOptions(\n this IServiceCollection collection, \n Func<IServiceProvider, AWSOptions> implementationFactory, \n ServiceLifetime lifetime = ServiceLifetime.Singleton)\n {\n collection.Add(new ServiceDescriptor(typeof(AWSOptions), implementationFactory, lifetime));\n \n return collection;\n }\n}\n```\n##### **Customizing AWSOptions based on an incoming HttpRequest**\nWith [isdaniel](https://github.com/isdaniel)’s [new extension method](https://github.com/aws/aws-sdk-net/pull/1958) to setup the ```IServiceCollection```, the next question is how to use deferred binding to customize ```AWSOptions``` based on an incoming ```HttpRequest```.\n\nWe can define and register a custom [ASP.NET Middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0) class to hook into the [ASP.NET](http://asp.net/) request pipeline and inspect the incoming request before the DI container is asked to construct any Controller classes. This is key, as the Controller depends on AWS Services, so in order for our customization to work it must execute before any AWS Service objects are built:\n\nC#\n```\npublic class Startup\n{\n // Bulk of Startup removed for brevity. See full example at the end of this\n // blog post for a complete working example.\n \n public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n {\n app.UseMiddleware<lRequestSpecificAWSOptionsMiddleware>();\n }\n}\n\npublic class RequestSpecificAWSOptionsMiddleware\n{\n private readonly RequestDelegate _next;\n\n public RequestSpecificAWSOptionsMiddleware(RequestDelegate next)\n {\n _next = next;\n }\n\n public async Task InvokeAsync(HttpContext context)\n {\n // TODO: inspect context and then somehow set AWSOptions ...\n }\n}\n```\n##### **Connecting ASP.NET Middleware with a DI Factory**\nHow can middleware influence the ```IServiceCollection```, which still must be initialized in the ```Startup.ConfigureServices``` method? The approach I came up with is to use an intermediate object to store a reference to a Factory function that will build the ```AWSOptions``` object. This function will be bound at Startup, but it won’t be defined until the ```RequestSpecificAWSOptionsMiddleware```executes. The key is that the object containing this function must also have a binding in the ```IServiceCollection```, so that it can be injected into the middleware:\n\nC#\n```\npublic interface IAWSOptionsFactory\n{\n /// <summary>\n /// Factory function for building AWSOptions that will be defined\n /// in a custom ASP.NET middleware.\n /// </summary>\n Func<AWSOptions> AWSOptionsBuilder { get; set; }\n}\n\npublic class AWSOptionsFactory : IAWSOptionsFactory\n{\n public Func<AWSOptions> AWSOptionsBuilder { get; set; }\n}\n```\nNow we can update our ```RequestSpecificAWSOptionsMiddlewareobject``` to consume ```IAWSOptionsFactory ```. Because we want the ```IAWSOptionsFactory ```to be Request Scope specific we can’t constructor inject the dependency, as Middleware objects have Singleton lifetimes. If we instead make it a method parameter, the [ASP.NET](http://asp.net/) runtime will know to treat the dependency as a Scoped lifetime, and generate a new object on every request:\n\nC#\n```\npublic class RequestSpecificAWSOptionsMiddleware\n{\n public async Task InvokeAsync(\n HttpContext context,\n IAWSOptionsFactory optionsFactory)\n {\n optionsFactory.AWSOptionsBuilder = () =>\n {\n var awsOptions = new AWSOptions();\n\n // SAMPLE: configure AWSOptions based on HttpContext,\n // get the region endpoint from the query string 'regionEndpoint' parameter\n if (context.Request.Query.TryGetValue(\"regionEndpoint\", out var regionEndpoint))\n {\n awsOptions.Region = RegionEndpoint.GetBySystemName(regionEndpoint);\n }\n\n return awsOptions;\n };\n\n await _next(context);\n }\n}\n```\nThe middleware now assigns an ```optionsFactory.AWSOptionsBuilder``` function to return a new ```AWSOptions``` object where the Region property is set by looking for a query string parameter named regionEndpoint in the incoming ```HttpRequest```.\n\n##### **Configuring Bindings**\n\nTo finalize the plumbing, we’ll need to add two new bindings.\n\nFirst we’ll need to bind ```IAWSOptionsFactory``` with a Scoped Lifecycle so that a new instance is created on every incoming ```HttpRequest```, and also so said instance can be injected both into the middleware’s invoke methods as well as into the ```AddDefaultAWSOptions``` factory method.\n\nThen we’ll bind ```AWSOptions``` using the new ```AddDefaultAWSOptions``` overload. We’ll use the passed-in reference to a ```ServiceProvider``` to get an instance of the ```IAWSOptionsFactory```. This is the same instance that will be passed to ```ResolveMutitenantMiddleware```, and we can then invoke ```AWSOptionsBuilder``` to build our request specific ```AWSOptions``` object!\n\nFinally, to prove it all works, I added a binding call to ```AddAWSService<IAmazonSimpleEmailServiceV2>()``` to provide me with an AWS Service that can be injected into my API Controller. However, the lifetime **must be explicitly** set to ```Scoped``` as this instructs the .NET Service Collection to always create a new instance of the Client when requested, which means it will re-evaluate the ```AWSOptions``` dependency used to build the Client:\n\nC#\n```\npublic class Startup\n{ \n public void ConfigureServices(IServiceCollection services)\n {\n // note: AWSOptionsFactory.AWSOptionsBuilder func will be populated in middleware\n services.AddScoped<IAWSOptionsFactory, AWSOptionsFactory>();\n services.AddDefaultAWSOptions(sp => sp.GetService<IAWSOptionsFactory>().AWSOptionsBuilder(), ServiceLifetime.Scoped));\n \n // also, for test purposes, register an AWSService that will consume the AWSOptions\n services.AddAWSService<IAmazonSimpleEmailServiceV2>(lifetime: ServiceLifetime.Scoped);\n }\n }\n```\n ##### **Time for an API Controller**\n\nTo show the whole app working we’ll need an API Controller. I’ve modified the ```ValuesController``` class that comes with the [ASP.NET ](http://asp.net/) API template to inject an ```IAmazonSimpleEmailServiceV2``` and return the region that the Service is configured to use:\n\nC#\n```\n[Route(\"api/[controller]\")]\npublic class ValuesController : ControllerBase\n{\n private readonly IAmazonSimpleEmailServiceV2 _amazonSimpleEmailService;\n\n public ValuesController(IAmazonSimpleEmailServiceV2 amazonSimpleEmailService)\n {\n _amazonSimpleEmailService = amazonSimpleEmailService;\n }\n\n // GET api/values\n [HttpGet]\n public IEnumerable Get()\n {\n return new string[]\n {\n _amazonSimpleEmailService.Config.RegionEndpoint.DisplayName\n };\n }\n}\n```\nIf we fire up the debugger we can now see the results of our hard work. Sending the sample requests below, we can see that our AWS Services are uniquely configured based on the incoming http request! Specifically, setting the ```regionEndpoint``` query string parameter changes the Region the Email Service is configured to use:\n\n\n ##### **Relative Url\t Resul**\n/api/Values?regionEndpoint=”us-east-1″\t[“US East (N. Virginia)”]\n/api/Values?regionEndpoint=”us-east-2″\t[“US East (Ohio)”]\n/api/values?regionEndpoint=eu-west-1\t[“Europe (Ireland)”]\n\n##### **Full Example**\n\nC#\n```\npublic interface IAWSOptionsFactory\n{\n\t/// <summary>\n\t/// Factory function for building AWSOptions that will be defined\n\t/// in a custom ASP.NET middleware.\n\t/// </summary>\n\tFunc<AWSOptions> AWSOptionsBuilder { get; set; }\n}\n\npublic class AWSOptionsFactory : IAWSOptionsFactory\n{\n\tpublic Func<IServiceProvider, AWSOptions> AWSOptionsBuilder { get; set; }\n}\n\n\npublic class Startup\n{\n\tpublic Startup(IConfiguration configuration)\n\t{\n\t\tConfiguration = configuration;\n\t}\n\n\tpublic static IConfiguration Configuration { get; private set; }\n\n\t// This method gets called by the runtime. Use this method to add services to the container\n\tpublic void ConfigureServices(IServiceCollection services)\n\t{\n\t\t// note: AWSOptionsFactory.AWSOptionsBuilder func will be populated in middleware\n\t\tservices.AddScoped<IAWSOptionsFactory, AWSOptionsFactory>();\n\t\tservices.AddDefaultAWSOptions(sp => sp.GetService<IAWSOptionsFactory>().AWSOptionsBuilder(), ServiceLifetime.Scoped));\n\t\t\n\t\t// also, for test purposes, register an AWSService that will consume the AWSOptions\n\t\tservices.AddAWSService<IAmazonSimpleEmailServiceV2>(lifetime: ServiceLifetime.Scoped);\n\n\t\tservices.AddControllers();\n\t}\n\n\t// This method gets called by the runtime. Use this method to configure the HTTP request pipeline\n\tpublic void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n\t{\n\t\tif (env.IsDevelopment())\n\t\t app.UseDeveloperExceptionPage();\n\t\t\n\n\t\tapp.UseHttpsRedirection();\n\n\t\tapp.UseRouting();\n\n\t\tapp.UseAuthorization();\n\n\t\t// Use Custom Middleware to build AWSOptions\n\t\tapp.UseMiddleware<RequestSpecificAWSOptionsMiddleware>();\n\n\t\tapp.UseEndpoints(endpoints =>\n\t\t{\n\t\t\tendpoints.MapControllers();\n\t\t\tendpoints.MapGet(\"/\", async context =>\n\t\t\t{\n\t\t\t\tawait context.Response.WriteAsync(\"Welcome to running ASP.NET Core on AWS Lambda\");\n\t\t\t});\n\t\t});\n\t}\n}\n\npublic class RequestSpecificAWSOptionsMiddleware\n{\n\tpublic async Task InvokeAsync(\n\t\tHttpContext context,\n\t\tIAWSOptionsFactory optionsFactory)\n\t{\n\t\toptionsFactory.AWSOptionsBuilder = () =>\n\t\t{\n\t\t\tvar awsOptions = new AWSOptions();\n\n\t\t\t// SAMPLE: configure AWSOptions based on HttpContext,\n\t\t\t// get the region endpoint from the query string 'regionEndpoint' parameter\n\t\t\tif (context.Request.Query.TryGetValue(\"regionEndpoint\", out var regionEndpoint))\n\t\t\t{\n\t\t\t\tawsOptions.Region = RegionEndpoint.GetBySystemName(regionEndpoint);\n\t\t\t}\n\n\t\t\treturn awsOptions;\n\t\t};\n\n\t\tawait _next(context);\n\t}\n}\n\n[Route(\"api/[controller]\")]\npublic class ValuesController : ControllerBase\n{\n\tprivate readonly IAmazonSimpleEmailServiceV2 _amazonSimpleEmailService;\n\n\tpublic ValuesController(IAmazonSimpleEmailServiceV2 amazonSimpleEmailService)\n\t{\n\t\t_amazonSimpleEmailService = amazonSimpleEmailService;\n\t}\n\n\t// GET api/values\n\t// Return the RegionEndpoint DisplayName. This visualizes that\n\t// the AmazonSimpleEmailService config is set in \n\t// RequestSpecificAWSOptionsMiddleware\n\t[HttpGet]\n\tpublic IEnumerable<string> Get()\n\t{\n\t\treturn new string[]\n\t\t{\n\t\t\t_amazonSimpleEmailService.Config.RegionEndpoint.DisplayName\n\t\t};\n\t}\n}\n```\n##### **Special Thanks**\nSpecial thanks to [isdaniel](https://github.com/isdaniel) for the initial question and of course for the [PR (with unit tests!!)](https://github.com/aws/aws-sdk-net/pull/1958). And thanks to [IgorPietraszko](https://github.com/IgorPietraszko) for further [engaging with us](https://github.com/aws/aws-aspnet-cognito-identity-provider/issues/210) and providing the [ASP.NET](http://asp.net/) middleware use-case.","render":"<h4><a id=\"Tips__Tricks_Delaying_AWS_Service_configuration_when_using_NET_Dependency_Injection_0\"></a><strong>Tips &amp; Tricks: Delaying AWS Service configuration when using .NET Dependency Injection</strong></h4>\n<p>The <a href=\"https://www.nuget.org/packages/AWSSDK.Extensions.NETCore.Setup/\" target=\"_blank\">AWSSDK.Extensions.NETCore.Setup</a> package provides extensions for enabling AWS Service Client creation to work with <a href=\"https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection\" target=\"_blank\">native .NET Dependency Injection</a>. Bindings for one or more services can be registered via the included<code>ddAWSService&lt;TService&gt;</code>method and a shared configuration can be added and customized via the <code>AddDefaultAWSOptions</code> method.</p>\n<p>C#</p>\n<pre><code class=\"lang-\">public class Startup\n{ \n public void ConfigureServices(IServiceCollection services)\n {\n // support injecting email client\n services.AddAWSService&lt;IAmazonSimpleEmailServiceV2&gt;();\n // customize amazon clients\n services.AddDefaultAWSOptions(new AWSOptions\n {\n Region = RegionEndpoint.USWest2\n });\n }\n}\n</code></pre>\n<p>Recently, several customers reported in the <a href=\"https://github.com/aws/aws-sdk-net\" target=\"_blank\">AWS .NET SDK GitHub repository</a> that they wanted to setup Dependency Injection (DI) for AWS Services and customize their configuration. In a traditional .NET Core application the DI Container, <code>IServiceCollection</code>, is initialized early during app startup in the Startup class. However, what if you wanted to defer initializing the <code>AWSOptions</code> object until later in the application lifecycle? What if you had an <a href=\"http://asp.net/\" target=\"_blank\">ASP.NET</a> Core application and wanted to customize <code>AWSOptions</code> based on the incoming request as was requested in this <a href=\"https://github.com/aws/aws-aspnet-cognito-identity-provider/issues/210\" target=\"_blank\">issue</a>?</p>\n<p>C#</p>\n<pre><code class=\"lang-\">public void ConfigureServices(IServiceCollection services)\n{\n services.AddDefaultAWSOptions(new AWSOptions\n {\n // My app doesn't _yet_ have the data needed to configure AWSOptions!\n });\n}\n</code></pre>\n<p>While it’s always been technically possible to add the necessary deferred binding, doing so required a deep understanding of <code>IServiceCollection</code>. Fortunately, <a href=\"https://github.com/isdaniel\" target=\"_blank\">isdaniel</a> recently sent the <a href=\"https://github.com/aws/aws-sdk-net/pull/1958\" target=\"_blank\">AWS .NET SDK</a> team a PR that <a href=\"https://github.com/aws/aws-sdk-net/pull/1958/files#diff-07243a2e5685453e039ccbfd7b0e0260136ca42d737ca017f0c86a89ea303cadR43\" target=\"_blank\">added</a> an overload to the <code>AddDefaultAWSOptions</code> method to greatly simplify adding a deferred binding:</p>\n<p>C#</p>\n<pre><code class=\"lang-\">public static class ServiceCollectionExtensions\n{\n public static IServiceCollection AddDefaultAWSOptions(\n this IServiceCollection collection, \n Func&lt;IServiceProvider, AWSOptions&gt; implementationFactory, \n ServiceLifetime lifetime = ServiceLifetime.Singleton)\n {\n collection.Add(new ServiceDescriptor(typeof(AWSOptions), implementationFactory, lifetime));\n \n return collection;\n }\n}\n</code></pre>\n<h5><a id=\"Customizing_AWSOptions_based_on_an_incoming_HttpRequest_49\"></a><strong>Customizing AWSOptions based on an incoming HttpRequest</strong></h5>\n<p>With <a href=\"https://github.com/isdaniel\" target=\"_blank\">isdaniel</a>’s <a href=\"https://github.com/aws/aws-sdk-net/pull/1958\" target=\"_blank\">new extension method</a> to setup the <code>IServiceCollection</code>, the next question is how to use deferred binding to customize <code>AWSOptions</code> based on an incoming <code>HttpRequest</code>.</p>\n<p>We can define and register a custom <a href=\"https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0\" target=\"_blank\">ASP.NET Middleware</a> class to hook into the <a href=\"http://asp.net/\" target=\"_blank\">ASP.NET</a> request pipeline and inspect the incoming request before the DI container is asked to construct any Controller classes. This is key, as the Controller depends on AWS Services, so in order for our customization to work it must execute before any AWS Service objects are built:</p>\n<p>C#</p>\n<pre><code class=\"lang-\">public class Startup\n{\n // Bulk of Startup removed for brevity. See full example at the end of this\n // blog post for a complete working example.\n \n public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n {\n app.UseMiddleware&lt;lRequestSpecificAWSOptionsMiddleware&gt;();\n }\n}\n\npublic class RequestSpecificAWSOptionsMiddleware\n{\n private readonly RequestDelegate _next;\n\n public RequestSpecificAWSOptionsMiddleware(RequestDelegate next)\n {\n _next = next;\n }\n\n public async Task InvokeAsync(HttpContext context)\n {\n // TODO: inspect context and then somehow set AWSOptions ...\n }\n}\n</code></pre>\n<h5><a id=\"Connecting_ASPNET_Middleware_with_a_DI_Factory_82\"></a><strong>Connecting ASP.NET Middleware with a DI Factory</strong></h5>\n<p>How can middleware influence the <code>IServiceCollection</code>, which still must be initialized in the <code>Startup.ConfigureServices</code> method? The approach I came up with is to use an intermediate object to store a reference to a Factory function that will build the <code>AWSOptions</code> object. This function will be bound at Startup, but it won’t be defined until the <code>RequestSpecificAWSOptionsMiddleware</code>executes. The key is that the object containing this function must also have a binding in the <code>IServiceCollection</code>, so that it can be injected into the middleware:</p>\n<p>C#</p>\n<pre><code class=\"lang-\">public interface IAWSOptionsFactory\n{\n /// &lt;summary&gt;\n /// Factory function for building AWSOptions that will be defined\n /// in a custom ASP.NET middleware.\n /// &lt;/summary&gt;\n Func&lt;AWSOptions&gt; AWSOptionsBuilder { get; set; }\n}\n\npublic class AWSOptionsFactory : IAWSOptionsFactory\n{\n public Func&lt;AWSOptions&gt; AWSOptionsBuilder { get; set; }\n}\n</code></pre>\n<p>Now we can update our <code>RequestSpecificAWSOptionsMiddlewareobject</code> to consume <code>IAWSOptionsFactory </code>. Because we want the <code>IAWSOptionsFactory </code>to be Request Scope specific we can’t constructor inject the dependency, as Middleware objects have Singleton lifetimes. If we instead make it a method parameter, the <a href=\"http://asp.net/\" target=\"_blank\">ASP.NET</a> runtime will know to treat the dependency as a Scoped lifetime, and generate a new object on every request:</p>\n<p>C#</p>\n<pre><code class=\"lang-\">public class RequestSpecificAWSOptionsMiddleware\n{\n public async Task InvokeAsync(\n HttpContext context,\n IAWSOptionsFactory optionsFactory)\n {\n optionsFactory.AWSOptionsBuilder = () =&gt;\n {\n var awsOptions = new AWSOptions();\n\n // SAMPLE: configure AWSOptions based on HttpContext,\n // get the region endpoint from the query string 'regionEndpoint' parameter\n if (context.Request.Query.TryGetValue(&quot;regionEndpoint&quot;, out var regionEndpoint))\n {\n awsOptions.Region = RegionEndpoint.GetBySystemName(regionEndpoint);\n }\n\n return awsOptions;\n };\n\n await _next(context);\n }\n}\n</code></pre>\n<p>The middleware now assigns an <code>optionsFactory.AWSOptionsBuilder</code> function to return a new <code>AWSOptions</code> object where the Region property is set by looking for a query string parameter named regionEndpoint in the incoming <code>HttpRequest</code>.</p>\n<h5><a id=\"Configuring_Bindings_131\"></a><strong>Configuring Bindings</strong></h5>\n<p>To finalize the plumbing, we’ll need to add two new bindings.</p>\n<p>First we’ll need to bind <code>IAWSOptionsFactory</code> with a Scoped Lifecycle so that a new instance is created on every incoming <code>HttpRequest</code>, and also so said instance can be injected both into the middleware’s invoke methods as well as into the <code>AddDefaultAWSOptions</code> factory method.</p>\n<p>Then we’ll bind <code>AWSOptions</code> using the new <code>AddDefaultAWSOptions</code> overload. We’ll use the passed-in reference to a <code>ServiceProvider</code> to get an instance of the <code>IAWSOptionsFactory</code>. This is the same instance that will be passed to <code>ResolveMutitenantMiddleware</code>, and we can then invoke <code>AWSOptionsBuilder</code> to build our request specific <code>AWSOptions</code> object!</p>\n<p>Finally, to prove it all works, I added a binding call to <code>AddAWSService&lt;IAmazonSimpleEmailServiceV2&gt;()</code> to provide me with an AWS Service that can be injected into my API Controller. However, the lifetime <strong>must be explicitly</strong> set to <code>Scoped</code> as this instructs the .NET Service Collection to always create a new instance of the Client when requested, which means it will re-evaluate the <code>AWSOptions</code> dependency used to build the Client:</p>\n<p>C#</p>\n<pre><code class=\"lang-\">public class Startup\n{ \n public void ConfigureServices(IServiceCollection services)\n {\n // note: AWSOptionsFactory.AWSOptionsBuilder func will be populated in middleware\n services.AddScoped&lt;IAWSOptionsFactory, AWSOptionsFactory&gt;();\n services.AddDefaultAWSOptions(sp =&gt; sp.GetService&lt;IAWSOptionsFactory&gt;().AWSOptionsBuilder(), ServiceLifetime.Scoped));\n \n // also, for test purposes, register an AWSService that will consume the AWSOptions\n services.AddAWSService&lt;IAmazonSimpleEmailServiceV2&gt;(lifetime: ServiceLifetime.Scoped);\n }\n }\n</code></pre>\n<h5><a id=\"Time_for_an_API_Controller_156\"></a><strong>Time for an API Controller</strong></h5>\n<p>To show the whole app working we’ll need an API Controller. I’ve modified the <code>ValuesController</code> class that comes with the <a href=\"http://asp.net/\" target=\"_blank\">ASP.NET </a> API template to inject an <code>IAmazonSimpleEmailServiceV2</code> and return the region that the Service is configured to use:</p>\n<p>C#</p>\n<pre><code class=\"lang-\">[Route(&quot;api/[controller]&quot;)]\npublic class ValuesController : ControllerBase\n{\n private readonly IAmazonSimpleEmailServiceV2 _amazonSimpleEmailService;\n\n public ValuesController(IAmazonSimpleEmailServiceV2 amazonSimpleEmailService)\n {\n _amazonSimpleEmailService = amazonSimpleEmailService;\n }\n\n // GET api/values\n [HttpGet]\n public IEnumerable Get()\n {\n return new string[]\n {\n _amazonSimpleEmailService.Config.RegionEndpoint.DisplayName\n };\n }\n}\n</code></pre>\n<p>If we fire up the debugger we can now see the results of our hard work. Sending the sample requests below, we can see that our AWS Services are uniquely configured based on the incoming http request! Specifically, setting the <code>regionEndpoint</code> query string parameter changes the Region the Email Service is configured to use:</p>\n<h5><a id=\"Relative_Url\t____________________Resul_186\"></a><strong>Relative Url\t Resul</strong></h5>\n<p>/api/Values?regionEndpoint=”us-east-1″\t[“US East (N. Virginia)”]<br />\n/api/Values?regionEndpoint=”us-east-2″\t[“US East (Ohio)”]<br />\n/api/values?regionEndpoint=eu-west-1\t[“Europe (Ireland)”]</p>\n<h5><a id=\"Full_Example_191\"></a><strong>Full Example</strong></h5>\n<p>C#</p>\n<pre><code class=\"lang-\">public interface IAWSOptionsFactory\n{\n\t/// &lt;summary&gt;\n\t/// Factory function for building AWSOptions that will be defined\n\t/// in a custom ASP.NET middleware.\n\t/// &lt;/summary&gt;\n\tFunc&lt;AWSOptions&gt; AWSOptionsBuilder { get; set; }\n}\n\npublic class AWSOptionsFactory : IAWSOptionsFactory\n{\n\tpublic Func&lt;IServiceProvider, AWSOptions&gt; AWSOptionsBuilder { get; set; }\n}\n\n\npublic class Startup\n{\n\tpublic Startup(IConfiguration configuration)\n\t{\n\t\tConfiguration = configuration;\n\t}\n\n\tpublic static IConfiguration Configuration { get; private set; }\n\n\t// This method gets called by the runtime. Use this method to add services to the container\n\tpublic void ConfigureServices(IServiceCollection services)\n\t{\n\t\t// note: AWSOptionsFactory.AWSOptionsBuilder func will be populated in middleware\n\t\tservices.AddScoped&lt;IAWSOptionsFactory, AWSOptionsFactory&gt;();\n\t\tservices.AddDefaultAWSOptions(sp =&gt; sp.GetService&lt;IAWSOptionsFactory&gt;().AWSOptionsBuilder(), ServiceLifetime.Scoped));\n\t\t\n\t\t// also, for test purposes, register an AWSService that will consume the AWSOptions\n\t\tservices.AddAWSService&lt;IAmazonSimpleEmailServiceV2&gt;(lifetime: ServiceLifetime.Scoped);\n\n\t\tservices.AddControllers();\n\t}\n\n\t// This method gets called by the runtime. Use this method to configure the HTTP request pipeline\n\tpublic void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n\t{\n\t\tif (env.IsDevelopment())\n\t\t app.UseDeveloperExceptionPage();\n\t\t\n\n\t\tapp.UseHttpsRedirection();\n\n\t\tapp.UseRouting();\n\n\t\tapp.UseAuthorization();\n\n\t\t// Use Custom Middleware to build AWSOptions\n\t\tapp.UseMiddleware&lt;RequestSpecificAWSOptionsMiddleware&gt;();\n\n\t\tapp.UseEndpoints(endpoints =&gt;\n\t\t{\n\t\t\tendpoints.MapControllers();\n\t\t\tendpoints.MapGet(&quot;/&quot;, async context =&gt;\n\t\t\t{\n\t\t\t\tawait context.Response.WriteAsync(&quot;Welcome to running ASP.NET Core on AWS Lambda&quot;);\n\t\t\t});\n\t\t});\n\t}\n}\n\npublic class RequestSpecificAWSOptionsMiddleware\n{\n\tpublic async Task InvokeAsync(\n\t\tHttpContext context,\n\t\tIAWSOptionsFactory optionsFactory)\n\t{\n\t\toptionsFactory.AWSOptionsBuilder = () =&gt;\n\t\t{\n\t\t\tvar awsOptions = new AWSOptions();\n\n\t\t\t// SAMPLE: configure AWSOptions based on HttpContext,\n\t\t\t// get the region endpoint from the query string 'regionEndpoint' parameter\n\t\t\tif (context.Request.Query.TryGetValue(&quot;regionEndpoint&quot;, out var regionEndpoint))\n\t\t\t{\n\t\t\t\tawsOptions.Region = RegionEndpoint.GetBySystemName(regionEndpoint);\n\t\t\t}\n\n\t\t\treturn awsOptions;\n\t\t};\n\n\t\tawait _next(context);\n\t}\n}\n\n[Route(&quot;api/[controller]&quot;)]\npublic class ValuesController : ControllerBase\n{\n\tprivate readonly IAmazonSimpleEmailServiceV2 _amazonSimpleEmailService;\n\n\tpublic ValuesController(IAmazonSimpleEmailServiceV2 amazonSimpleEmailService)\n\t{\n\t\t_amazonSimpleEmailService = amazonSimpleEmailService;\n\t}\n\n\t// GET api/values\n\t// Return the RegionEndpoint DisplayName. This visualizes that\n\t// the AmazonSimpleEmailService config is set in \n\t// RequestSpecificAWSOptionsMiddleware\n\t[HttpGet]\n\tpublic IEnumerable&lt;string&gt; Get()\n\t{\n\t\treturn new string[]\n\t\t{\n\t\t\t_amazonSimpleEmailService.Config.RegionEndpoint.DisplayName\n\t\t};\n\t}\n}\n</code></pre>\n<h5><a id=\"Special_Thanks_307\"></a><strong>Special Thanks</strong></h5>\n<p>Special thanks to <a href=\"https://github.com/isdaniel\" target=\"_blank\">isdaniel</a> for the initial question and of course for the <a href=\"https://github.com/aws/aws-sdk-net/pull/1958\" target=\"_blank\">PR (with unit tests!!)</a>. And thanks to <a href=\"https://github.com/IgorPietraszko\" target=\"_blank\">IgorPietraszko</a> for further <a href=\"https://github.com/aws/aws-aspnet-cognito-identity-provider/issues/210\" target=\"_blank\">engaging with us</a> and providing the <a href=\"http://asp.net/\" target=\"_blank\">ASP.NET</a> middleware use-case.</p>\n"}
目录
亚马逊云科技解决方案 基于行业客户应用场景及技术领域的解决方案
联系亚马逊云科技专家
亚马逊云科技解决方案
基于行业客户应用场景及技术领域的解决方案
联系专家
0
目录
关闭