Since the new .net 3.2 preview no longer has a startup.cs with its ConfigureServices function, I am at a loss to figure out how to implement .AddCors. The old way of adding services essentially was to add, then use a service. It doesn't look like that's the way to do it anymore. What is the proper code to add CORS?
Program.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Blazor.Hosting;
using Microsoft.Extensions.DependencyInjection;
using BlazorDemo.Shared;
namespace BlazorDemo
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddSingleton<IDataLayer, DataLayer>();
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyOrigin());
});
builder.RootComponents.Add<App>("app");
await builder.Build().RunAsync();
}
}
}
Data.cs
namespace BlazorDemo.Shared
{
public class Data
{
public Country[] data { get; set; }
}
}
DataLayer.cs
using System;
using System.Threading.Tasks;
using System.Net.Http;
using Microsoft.AspNetCore.Components;
namespace BlazorDemo.Shared
{
public interface IDataLayer
{
Task<Country[]> FetchCountries(string sortField, bool sortDesc);
Task<Country[]> FetchCountries();
}
public class DataLayer : IDataLayer
{
public DataLayer(HttpClient httpClient)
{
this.httpClient = httpClient;
}
HttpClient httpClient;
public async Task<Country[]> FetchCountries(string sortField, bool sortDesc)
{
var url = $"http://outlier.oliversturm.com:8080/countries?sort[0][selector]={sortField}&sort[0][desc]={sortDesc}&take=10";
var data = await httpClient.GetJsonAsync<Data>(url);
return data.data;
}
public async Task<Country[]> FetchCountries()
{
Country[] Countries;
try
{
var url = $"http://outlier.oliversturm.com:8080/countries";
var data = await httpClient.GetJsonAsync<Data>(url);
Countries = ((Data)data).data;
}
catch (Exception e)
{
Country c = new Country() { name = "DD", areaKM2 = 2, population = 50, _id = e.Message };
Countries = new Country[] { c };
}
return Countries;
}
}
}
Related
We are using ServiceStack for our .NET backend and I am trying to work on getting unit testing into the project. However there are some automated tools within ServiceStack that makes it a bit complicated to isolate the units so I could really use some advice. In the example below I would like to unit test a simple service that basically does the following:
Takes a request DTO
Passes the DTO to the repository
Gets back a domain model
If the model exists, it maps it to a responseDTO using Automapper and returns it as a part of an IHTTPResult
So the problem I have is that it seems like Automapper is automatically added to the ServiceStack application and in the application the mapper are registered by just calling:
AutoMapping.RegisterConverter().
So how could I inject this into the service to be able to do the unittest?
Example test:
using AutoMapper;
using FluentAssertions;
using NSubstitute;
namespace Api.Services.Tests.Unit;
public class OrderApiServiceTests
{
private readonly OrderApiService _sut;
private readonly IOrderApiRepository accountApiRepository = Substitute.For<IOrderApiRepository>();
public OrderApiServiceTests()
{
_sut = new OrderApiRepository(orderApiRepository);
var config = new MapperConfiguration(cfg => ApiDtoMapping.Register());
var mapper = config.CreateMapper();
}
[Fact]
public async Task Get_ShouldReturnAccount_WhenAccountExistsAsync()
{
// Arrange
var order = new Order
{
Name = "MyOrder",
Value = 1000,
};
var expectedResponse = new OrderApiDto
{
Name = "MyOrder",
Value = 1000,
};
orderApiRepository.GetAsync(Arg.Any<GetOrder>()).Returns(order);
// Act
var result = await _sut.Get(new GetOrder());
// Assert
result.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
result.Response.Should().BeEquivalentTo(expectedResponse);
}
}
Added a full example including all files:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
}
app.UseServiceStack(new AppHost());
app.Run();
// Configure.AppHost.cs
using Funq;
using ssUnitTests.ServiceInterface;
[assembly: HostingStartup(typeof(ssUnitTests.AppHost))]
namespace ssUnitTests;
public class AppHost : AppHostBase, IHostingStartup
{
public void Configure(IWebHostBuilder builder) => builder
.ConfigureServices(services =>
{
});
public AppHost() : base("ssUnitTests", typeof(MyServices).Assembly) { }
public override void Configure(Container container)
{
container.RegisterAutoWiredAs<OrderRepository, IOrderRepository>().ReusedWithin(ReuseScope.None);
// Configure ServiceStack only IOC, Config & Plugins
SetConfig(new HostConfig
{
UseSameSiteCookies = true,
});
Mappings.RegisterConverters();
}
}
// Mappings.cs
using ssUnitTests.ServiceModel;
namespace ssUnitTests;
public static class Mappings
{
public static void RegisterConverters()
{
AutoMapping.RegisterConverter((Order from) =>
{
var to = from.ConvertTo<OrderDto>();
to.DtoProperty = from.BaseProperty + "Dto";
return to;
});
}
}
// IOrderRepository.cs
using ssUnitTests.ServiceModel;
namespace ssUnitTests.ServiceInterface;
public interface IOrderRepository
{
Order GetOrder();
}
// Order.cs
namespace ssUnitTests.ServiceModel;
public class Order
{
public string Name { get; set; }
public string BaseProperty { get; set; }
}
// OrderDto.cs
namespace ssUnitTests.ServiceModel;
public class OrderDto
{
public string Name { get; set; }
public string DtoProperty { get; set; }
}
// OrderRequest.cs
using ServiceStack;
namespace ssUnitTests.ServiceModel;
[Route("/order")]
public class OrderRequest : IReturn<OrderDto>
{
public int Id { get; set; }
}
// UnitTest.cs
using NSubstitute;
using NUnit.Framework;
using ssUnitTests.ServiceInterface;
using ssUnitTests.ServiceModel;
namespace ssUnitTests.Tests;
public class UnitTest
{
private readonly MyServices _sut;
private readonly IOrderRepository _repository = Substitute.For<IOrderRepository>();
public UnitTest()
{
_sut = new MyServices(_repository);
}
[Test]
public void Get_ShouldReturn_OrderDto()
{
var order = new Order
{
Name = "MyName",
BaseProperty = "MyBaseProperty"
};
_repository.GetOrder().Returns(order);
var response = (OrderDto)_sut.Any(new OrderRequest { Id = 1 });
Assert.That(response.Name.Equals(order.Name));
Assert.That(response.DtoProperty.Equals(order.BaseProperty + "Dto"));
}
}
ServiceStack.dll does not have any dependencies to any 3rd Party Libraries, e.g. it's built-in AutoMapping is a completely different stand-alone implementation to AutoMapper.
If you're using AutoMapper you can ignore ServiceStack's AutoMapping which is completely unrelated.
I have a Azure Function with 2 triggers:
I’m registering IService in my Startup like so:
I need a different configuration in the Service class depending on which trigger that is calling DoWork()? How can I achieve this using DI?
public class Service : IService
{
public Service(/*Configuration to be injected depends on calling trigger */)
{ }
public void DoWork()
{ }
}
Configuration extract:
Thankyou user1672994. Posting your suggestion as an answer so that it will be helpful for other community members who face similar kind of issues.
Below is the example code to implement todo work items where this will be helpful in resolving your issue.
using AZV3CleanArchitecture.Models;
using AZV3CleanArchitecture.Options;
using AZV3CleanArchitecture.Providers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
namespace AZV3CleanArchitecture.Services
{
public class ToDoItemsService : IToDoItemsService
{
private readonly HttpClient httpClient;
private readonly ToDoItemsServiceOptions toDoItemsServiceOptions;
private readonly ILogger<ToDoItemsService> logger;
public ToDoItemsService(HttpClient httpClient, IOptions<ToDoItemsServiceOptions> toDoItemsServiceOptions, ILogger<ToDoItemsService> logger)
{
this.httpClient = httpClient;
this.toDoItemsServiceOptions = toDoItemsServiceOptions.Value;
this.logger = logger;
}
public async Task<ToDoItem> GetToDoItem(int id)
{
logger.LogInformation($"Retrieving item: {{{Constants.TodoItemId}}}", id);
var getUrl = $"{this.toDoItemsServiceOptions.BaseUrl.TrimEnd('/')}/todos/{id}";
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, getUrl))
{
using (var response = await this.httpClient.SendAsync(requestMessage))
{
string responseString = await response.Content.ReadAsStringAsync();
logger.LogWarning($"Retrieved item: {{{Constants.TodoItemId}}}. Logged as warning for demo.", id);
return JsonConvert.DeserializeObject<ToDoItem>(responseString);
}
}
}
public async Task<IEnumerable<ToDoItem>> GetAllToDoItems(int id)
{
logger.LogInformation($"Retrieving all todo items");
var getUrl = $"{this.toDoItemsServiceOptions.BaseUrl.TrimEnd('/')}/todos";
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, getUrl))
{
using (var response = await this.httpClient.SendAsync(requestMessage))
{
string responseString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<IEnumerable<ToDoItem>>(responseString);
}
}
}
public async Task<ToDoItem> CreateToDoItem(ToDoItem toDoItem)
{
// call service and return the output
return await Task.FromResult(new ToDoItem() { Id = 1, UserId = 1, Title = "Some Dummy Title", Completed = true });
}
public Task<ToDoItem> UpdateToDoItem(ToDoItem toDoItem)
{
throw new System.NotImplementedException();
}
}
}
for further information check the ToDoItemServices link.
I have a unit test that is basically testing the behaviour of EF Core. The class I am trying to test looks like this:
namespace MusicPortal.Repository.Repository
{
public class ArtistRepository : IArtistRepository
{
private readonly MusicPortalDbContext _context;
public ArtistRepository(MusicPortalDbContext context)
{
_context = context;
}
public async Task<MusicPortalDatabaseResponse<bool>> AddNewArtist(Artist artist)
{
try
{
await _context.Artists.AddAsync(new Artist
{
ArtistType = ArtistTypes.Band,
City = artist.City,
Country = artist.Country,
Genre = artist.Genre,
Name = artist.Name,
ProfileImageUrl = artist.ProfileImageUrl
});
_context.SaveChanges();
return new MusicPortalDatabaseResponse<bool>
{
HasError = false,
Exception = null,
Response = true
};
}
catch (Exception e)
{
return new MusicPortalDatabaseResponse<bool>
{
HasError = true,
Exception = e,
Response = false
};
}
}
}
}
And I have the following Unit Test for it using Moq
namespace MusicPortal.Tests.Repository.ArtistRepository.AddNewArtist
{
[TestFixture]
public class GivenAddingANewArtistToADatabaseFails
{
private Mock<DbSet<Artist>> _mockArtistDbSet;
private Mock<MusicPortalDbContext> _mockContext;
private IArtistRepository _artistRepository;
private MusicPortalDatabaseResponse<bool> _addArtistToDbResponse;
[OneTimeSetUp]
public async Task Setup()
{
_mockArtistDbSet = new Mock<DbSet<Artist>>();
_mockContext = new Mock<MusicPortalDbContext>();
_mockArtistDbSet
.Setup(x => x.AddAsync(It.IsAny<Artist>(), It.IsAny<CancellationToken>()))
.Callback((Artist artist, CancellationToken token) => { })
.ReturnsAsync(It.IsAny<EntityEntry<Artist>>());
_mockContext
.Setup(x => x.SaveChanges())
.Throws(new Exception("Cannot save new Artist to Database"));
_artistRepository = new MusicPortal.Repository.Repository.ArtistRepository(_mockContext.Object);
_addArtistToDbResponse = await _artistRepository.AddNewArtist(It.IsAny<Artist>());
}
[Test]
public void ThenANegativeResultIsReturned() // pass
{
Assert.IsFalse(_addArtistToDbResponse.Response);
Assert.IsTrue(_addArtistToDbResponse.HasError);
Assert.IsInstanceOf<Exception>(_addArtistToDbResponse.Exception);
}
[Test]
public void ThenTheArtistContextAddMethodIsCalledOnce() //fail
{
_mockArtistDbSet.Verify(x => x.AddAsync(It.IsAny<Artist>(), It.IsAny<CancellationToken>()), Times.Once);
}
[Test]
public void ThenTheArtistsContextSaveMethodIsNeverCalled() //pass
{
_mockContext.Verify(x => x.SaveChanges(), Times.Never);
}
}
}
The first and last assertion pass but ThenTheArtistContextAddMethodIsCalledOnce() fails due to the following error:
MusicPortal.Tests.Repository.ArtistRepository.AddNewArtist.GivenAddingANewArtistToADatabaseFails.ThenTheArtistContextAddMethodIsCalledOnce
Moq.MockException :
Expected invocation on the mock once, but was 0 times: x => x.AddAsync(It.IsAny(), It.IsAny())
Performed invocations:
Mock:1> (x):
No invocations performed.
at Moq.Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage)
at Moq.Mock1.Verify[TResult](Expression1 expression, Func`1 times)
at MusicPortal.Tests.Repository.ArtistRepository.AddNewArtist.GivenAddingANewArtistToADatabaseFails.ThenTheArtistContextAddMethodIsCalledOnce() in MusicPortal.Tests\Repository\ArtistRepository\AddNewArtist\GivenAddingANewArtistToADatabaseFails.cs:line 53
I'm understanding that the problem code is c#
_mockArtistDbSet
.Setup(x => x.AddAsync(It.IsAny<Artist>(), It.IsAny<CancellationToken>()))
.Callback((Artist artist, CancellationToken token) => { })
.ReturnsAsync(It.IsAny<EntityEntry<Artist>>());
And I know the problem is most likely due to async issue but I don't know why, or what the actual problem is. Any advice, solutions?
You declare and setup _mockArtistDbSet, but you don't use/attach it to the _mockContext. I think you need to add something like:
_mockContext.Setup(m => m.Artist).Returns(_mockArtistDbSet.Object);
So it looks like EF Core is not so easily tested with async tasks such as SaveChangesAsync and AddAsync. In the end, I followed the MS guide for testing EF core and created a mock context. The only downside being I can only test happy path. Although error paths are tested by the service which consumes the repository, I was hoping for more test coverage on the repository layer.
Anyway, here's the spec
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using MusicPortal.Core.Context;
using MusicPortal.Core.Repository;
using MusicPortal.Tests.Repository.ArtistRepository.TestHelpers;
using MusicPortal.Tests.Repository.ArtistRepository.TestHelpers.MockDB;
using NUnit.Framework;
using MockArtistRepository = MusicPortal.Repository.Repository.ArtistRepository;
namespace MusicPortal.Tests.Repository.ArtistRepository.AddNewArtist
{
[TestFixture]
public class GivenANewArtistToInsertIntoTheDb
{
private DbContextOptions<MusicPortalDbContext> _options;
private MusicPortalDatabaseResponse<bool> _mockResponse;
[OneTimeSetUp]
public async Task Setup()
{
_options = new MockDbFactory("MusicPortalDB").Options;
using (var context = new MusicPortalDbContext(_options))
{
var artistRepository = new MockArtistRepository(context);
_mockResponse = await artistRepository.AddNewArtist(MockRepositoryData.Artist);
}
}
[Test]
public void AndThenAPositiveResultIsReturned()
{
Assert.Null(_mockResponse.Exception);
Assert.IsTrue(_mockResponse.Response);
Assert.IsFalse(_mockResponse.HasError);
}
[Test]
public void ThenTheArtistShouldBeSavedWithNoProblem()
{
using (var context = new MusicPortalDbContext(_options))
{
Assert.AreEqual(1, context.Artists.Count());
}
}
}
}
and the Mock Database:
using System;
using Microsoft.EntityFrameworkCore;
using MusicPortal.Core.Context;
using MusicPortal.Core.DBModels;
namespace MusicPortal.Tests.Repository.ArtistRepository.TestHelpers.MockDB
{
public sealed class MockDbFactory
{
public DbContextOptions<MusicPortalDbContext> Options { get; }
public MockDbFactory(string dbName)
{
Options = new DbContextOptionsBuilder<MusicPortalDbContext>()
.UseInMemoryDatabase(dbName)
.Options;
}
public void AddArtistsToContext()
{
using (var context = new MusicPortalDbContext(Options))
{
context.Artists.Add(new Artist
{
City = "Orange County",
Country = "USA",
Events = null,
Genre = "Pop Punk",
Id = Guid.Parse("8a07504b-8152-4d8b-8e21-74bf64322ebc"),
Merchandise = null,
Name = "A Day To Remember",
ArtistType = "Band",
ProfileImageUrl = "https://placehold.it/30x30"
});
context.SaveChanges();
}
}
}
}
I hope this helps anyone looking at the same issue. The lesson learned is don't use Async unless you absolutely have to.
I can't seem to get my code work, although I tried several different approaches. Here is my preferred code snippet:
var client = await ApiClientProvider.GetApiClient();
var freeToPlayChampions = await client.GetChampionsAsync(true, Region);
var championsData = freeToPlayChampions.Select(x =>
client.GetStaticChampionByIdAsync(
(int)x.Id,
platformId: Region));
ConsoleTable.From(await Task.WhenAll(championsData)).Write();
When debugging I see that the code hangs on await Task.WhenAll(championsData). So i tried to make the code more easy:
var client = await ApiClientProvider.GetApiClient();
var freeToPlayChampions = await client.GetChampionsAsync(true, Region);
var table = new ConsoleTable();
foreach(var freeToPlayChampion in freeToPlayChampions)
{
var championsData = client.GetStaticChampionByIdAsync(
(int)freeToPlayChampion.Id,
platformId: Region);
table.AddRow(await championsData);
}
table.Write();
Unfortunately this hangs, as well. Again on the same code part, e.g. await championsData.
How can this 'easy' usage of async/await lead to an deadlock? Thanks in advance for help!
EDIT:
Here is the whole class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ConsoleTables;
using Mono.Options;
using RiotNet.Models;
using RiotShell.Properties;
namespace RiotShell
{
public class FreeToPlay : IShellCommand
{
public IEnumerable<string> Names { get; }
public OptionSet Options { get; }
public bool ShowHelp { get; private set; }
public string Region { get; private set; }
public FreeToPlay()
{
Names = new List<string>
{
"freetoplay",
"ftp"
};
Options = new OptionSet
{
{ "r|region=" , "The region to execute against", x => Region = x},
{ "h|help|?" , "Show help", x => ShowHelp = true }
};
}
public async Task Execute(IEnumerable<string> args)
{
if (ShowHelp)
{
Options.WriteOptionDescriptions(Console.Out);
return;
}
if (args.Any())
{
throw new Exception(Resources.TooManyArgumentsProvided);
}
if (Region == null)
{
throw new Exception(string.Format(Resources.RequiredOptionNotFound, "region"));
}
if (!PlatformId.All.Contains(Region))
{
throw new Exception(string.Format(Resources.InvalidRegion, Region));
}
var client = await ApiClientProvider.GetApiClient();
var freeToPlayChampions = await client.GetChampionsAsync(true, Region);
var championsData = freeToPlayChampions.Select(x =>
client.GetStaticChampionByIdAsync(
(int)x.Id,
platformId: Region));
ConsoleTable.From(await Task.WhenAll(championsData)).Write();
}
}
}
And here is the caller code, my main method:
using System;
using System.Threading.Tasks;
using RiotShell.Properties;
namespace RiotShell
{
public class Program
{
public static async Task Main()
{
while (true)
{
Console.Write(Resources.RiotShellLineString);
var input = Console.ReadLine();
try
{
var parsedArgs = InputParser.Parse(input);
(var command, var commandArgs) = ArgsToIShellCommandCaster.GetCommand(parsedArgs);
await command.Execute(commandArgs);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
}
Since it was wished, here the code for the ApiProvider:
using RiotNet;
using System.Threading.Tasks;
namespace RiotShell
{
public class ApiClientProvider
{
private static IRiotClient _client;
public static async Task<IRiotClient> GetApiClient()
{
if (_client != null)
{
_client.Settings.ApiKey = await KeyService.GetKey();
return _client;
}
_client = new RiotClient(new RiotClientSettings
{
ApiKey = await KeyService.GetKey()
});
return _client;
}
}
}
I am trying to implement step-by-step selfhosting OWIN application following this article. I've done all examples before 'Use Configuration Objects for Configuring Middleware' section, but during coding the example from that section i've got error. Here my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
// Add the Owin Usings:
using Owin;
using Microsoft.Owin.Hosting;
using Microsoft.Owin;
namespace WebAPI
{
// use an alias for the OWIN AppFunc:
using AppFunc = Func<IDictionary<string, object>, Task>;
class Program
{
static void Main(string[] args)
{
WebApp.Start<Startup>("http://localhost:8080");
Console.WriteLine("Server Started; Press enter to Quit");
Console.ReadLine();
}
}
public class MyMiddlewareConfigOptions
{
string _greetingTextFormat = "{0} from {1}{2}";
public MyMiddlewareConfigOptions(string greeting, string greeter)
{
GreetingText = greeting;
Greeter = greeter;
Date = DateTime.Now;
}
public string GreetingText { get; set; }
public string Greeter { get; set; }
public DateTime Date { get; set; }
public bool IncludeDate { get; set; }
public string GetGreeting()
{
string DateText = "";
if (IncludeDate)
{
DateText = string.Format(" on {0}", Date.ToShortDateString());
}
return string.Format(_greetingTextFormat, GreetingText, Greeter, DateText);
}
}
public static class AppBuilderExtensions
{
public static void UseMyMiddleware(this IAppBuilder app, MyMiddlewareConfigOptions configOptions)
{
app.Use<MyMiddlewareComponent>(configOptions);
}
public static void UseMyOtherMiddleware(this IAppBuilder app)
{
app.Use<MyOtherMiddlewareComponent>();
}
}
public class MyMiddlewareComponent
{
AppFunc _next;
// Add a member to hold the greeting:
string _greeting;
public MyMiddlewareComponent(AppFunc next, string greeting)
{
_next = next;
_greeting = greeting;
}
public async Task Invoke(IDictionary<string, object> environment)
{
IOwinContext context = new OwinContext(environment);
// Insert the _greeting into the display text:
await context.Response.WriteAsync(string.Format("<h1>{0}</h1>", _greeting));
await _next.Invoke(environment);
}
}
public class MyOtherMiddlewareComponent
{
AppFunc _next;
public MyOtherMiddlewareComponent(AppFunc next)
{
_next = next;
}
public async Task Invoke(IDictionary<string, object> environment)
{
IOwinContext context = new OwinContext(environment);
await context.Response.WriteAsync("<h1>Hello from My Second Middleware</h1>");
await _next.Invoke(environment);
}
}
public class Startup
{
public void Configuration(IAppBuilder app)
{
// Set up the configuration options:
var options = new MyMiddlewareConfigOptions("Greetings!", "John");
options.IncludeDate = true;
// Pass options along in call to extension method:
//app.UseMyMiddleware(options);
app.Use<MyMiddlewareComponent>(options);
app.UseMyOtherMiddleware();
}
}
}
The class 'WebAPI.MyMiddlewareComponent' does not have a constructor taking 2 arguments.
when app.Use<MyMiddlewareComponent>(options); is calling. If i use some string instead of MyMiddlewareConfigOptions:
app.Use<MyMiddlewareComponent>("somestring");
it works.
Version of Owin package is 3.0.1.0, .NET Framework - 4.5.
Why it is happening?
Oh, i figured it out... It was article's mistake: this part in MyMiddlewareComponent class
public MyMiddlewareComponent(AppFunc next, string greeting)
{
_next = next;
_greeting = greeting;
}
should be replaced by that
public MyMiddlewareComponent(AppFunc next, MyMiddlewareConfigOptions options)
{
_next = next;
_greeting = options.GetGreeting();
}
Now it works.