How does one transform SOAP EndpointAddress in .NET Core? - asp.net-core

When connecting a SOAP service in .NET Core the Connected Service is shown as expected in the solution explorer
The ConnectedService.json does contain the definitions as supposed. I.e.
{
"ProviderId": "Microsoft.VisualStudio.ConnectedService.Wcf",
...
"ExtendedData": {
"Uri": "https://test.example.net/Service.svc",
"Namespace": "UserWebService",
"SelectedAccessLevelForGeneratedClass": "Public",
...
}
The Uri from ExtendedData ends up in the Reference.cs file
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.WSHttpBinding_IAnvandareService))
{
return new System.ServiceModel.EndpointAddress("https://test.example.net/Service.svc");
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
If a deployment process looks like TEST > STAGING > PRODUCTION one might like to have corresponding endpoints. I.e. https://production.example.net/Service.svc.
We use Azure Devops for build and Azure Devops/Octopus Deploy for deployments

The solution (as I figured) was to change the endpoint address when you register the dependency i.e.
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
services.AddTransient<IAnvandareService, AnvandareServiceClient>((ctx) => new AnvandareServiceClient()
{
Endpoint =
{
Address = new EndpointAddress($"https://{environment}.example.net/Service.svc")
}
});

This is just an expansion of the answer provided by Eric Herlitz. Primarily meant to show how to use your appsettings.json file to hold the value for the endpoint url.
You will need to add the different endpoints to your appsettings.{enviroment}.json files.
{
...
"ServiceEndpoint": "http://someservice/service1.asmx",
...
}
Then you will need to make sure your environment variable is updated when you publish to different environments. How to transform appsettings.json
In your startup class find the method ConfigureServices() and register your service for dependency injection
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ADSoap, ADSoapClient>(fac =>
{
var endpoint = Configuration.GetValue<string>("ServiceEndpoint");
return new ADSoapClient(ADSoapClient.EndpointConfiguration.ADSoap12)
{
Endpoint =
{
Address = new EndpointAddress(new Uri(endpoint))
}
};
});
}
Then to consume the service in some class you can inject the service into the constructor:
public class ADProvider : BaseProvider, IADProvider
{
public ADSoap ADService { get; set; }
public ADProvider(IAPQ2WebApiHttpClient httpClient, IMemoryCache cache, ADSoap adClient) : base(httpClient, cache)
{
ADService = adClient;
}
}

Related

How to access appsettings.json from module Configure Services

I have a blazor server side solution that contains an appsettings.json
I am configuring the blob storage in the ConfigureServices override in the Application Module of the Applications project. It currently has a hard coded connection string and is working perfectly.
Now I want to move the connection string to the appsettings.json file that is in the Blazor project.
I've tried to inject the IConfiguration into the constructor of the ApplicationModule, but the app throws an error when I try to do so.
I've searched through the ServiceConfigurationContext passed into to the ConfigureServices override. There is a Service property containing a collection of around 1,024 ServiceDescriptors and found one that contains the word IConfiguration in the ServiceType.FullName but haven't been able to figure out how to use it to get at the service itself in order to get at the appsettings.json values.
Can anyone shed any light on how to access the appsettings.json values from the application module?
Here is my code I am working with
namespace MyApp
{
[DependsOn(
typeof(MyAppDomainModule),
typeof(AbpAccountApplicationModule),
typeof(MyAppApplicationContractsModule),
typeof(AbpIdentityApplicationModule),
typeof(AbpPermissionManagementApplicationModule),
typeof(AbpTenantManagementApplicationModule),
typeof(AbpFeatureManagementApplicationModule),
typeof(AbpSettingManagementApplicationModule),
typeof(AbpBlobStoringModule),
typeof(AbpBlobStoringAzureModule)
)]
public class MyAppApplicationModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseAzure(azure =>
{
azure.ConnectionString = "DefaultEndpointsProtocol=https;AccountName=MyApplocalsa;AccountKey=<truncated-account-key>;EndpointSuffix=core.windows.net";
azure.ContainerName = "Pictures";
azure.CreateContainerIfNotExists = true;
});
});
});
}
}
}
This answer has been update based on new information in the question.
If I understand the context correctly you are building your own DI services container within MyAppApplicationModule. As I don't have enough detail on MyAppApplicationModule, I'll demonstrate how you get to apllication configuration data in the context of OwningComponentBase which also defines it's own DI services container. Note I'm using Net6.0 here.
First the configuation data in appsettings.json of the web project.
{
"AzureData": {
"ConnectionString": "DefaultEndpointsProtocol=https;AccountName=MyApplocalsa;AccountKey=<truncated-account-key>;EndpointSuffix=core.windows.net",
"ContainerName": "Pictures"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Next define a data class to hold the configuration data
public class AzureData
{
public readonly Guid Id = Guid.NewGuid();
public string ConnectionString { get; set; } = string.Empty;
public string ContainerName { get; set; } = string.Empty;
}
Now register a configuration instance binding an AzureData instance against a section in the configuration file.
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.Configure<AzureData>(builder.Configuration.GetSection("AzureData"));
Finally the component.
Note:
We use IOptions<AzureData> to get the specific configuration instance, and Value to get the actual object.
AzureData is the same DI object, inside or outside the local service container. It's defined as a singleton.
#page "/di"
#inherits OwningComponentBase
#using Microsoft.Extensions.Options
<h3>DI Component</h3>
<div class="m-2 p-2">
Main Service Container <br />
Id: #AzureDataConfig?.Value.Id <br />
Connection String: #AzureDataConfig?.Value.ConnectionString
</div>
<div class="m-2 p-2">
Component Service Container <br />
Id:#azureData?.Value.Id <br />
Connection String: #azureData?.Value.ConnectionString
</div>
#code {
[Inject] private IOptions<AzureData>? AzureDataConfig { get; set; }
private IOptions<AzureData>? azureData;
protected override void OnInitialized()
{
azureData = ScopedServices.GetService<IOptions<AzureData>>();
base.OnInitialized();
}
}
I finally figured out the answer to the question by looking at other modules in the solution.
Here is the updated code
namespace MyApp
{
[DependsOn(
typeof(MyAppDomainModule),
typeof(AbpAccountApplicationModule),
typeof(MyAppApplicationContractsModule),
typeof(AbpIdentityApplicationModule),
typeof(AbpPermissionManagementApplicationModule),
typeof(AbpTenantManagementApplicationModule),
typeof(AbpFeatureManagementApplicationModule),
typeof(AbpSettingManagementApplicationModule),
typeof(AbpBlobStoringModule),
typeof(AbpBlobStoringAzureModule)
)]
public class MyAppApplicationModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<MyAppApplicationModule>();
});
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseAzure(azure =>
{
azure.ConnectionString = configuration.GetSection("BlobStorage:ConnectionString").Value;
azure.ContainerName = configuration.GetSection("BlobStorage:ContainerName").Value;
azure.CreateContainerIfNotExists = true;
});
});
});
}
}
}
I needed to add the using
using Microsoft.Extensions.DependencyInjection;
I was able to get a reference to the configuration
var configuration = context.Services.GetConfiguration();
I updated the hard coded connection string with retrieving it from the configuration.
azure.ConnectionString = configuration.GetSection("BlobStorage:ConnectionString").Value;
azure.ContainerName = configuration.GetSection("BlobStorage:ContainerName").Value;
I updated the appsettings.json file in my Blazor app
"BlobStorage": {
"ConnectionString": "DefaultEndpointsProtocol=https;AccountName=myapplocalsa;AccountKey=<truncated>;EndpointSuffix=core.windows.net",
"ContainerName" : "Pictures"
}
That was it!
Thank you Joe for investing your time in providing answers to my question!
For others who might be looking for a solution to the same problem - I have a couple of things to add. I was using a separated tenant with separate Product.IdentityServer, Product.Web, and Product.HttpApi.Host projects.
The configuration I was trying to perform was for the AbpTwilioSmsModule and AbpBlobStoringModule. The values for these modules were hardcoded into my
Product.Domain.ProductDomainModule class.
// TODO - Need to configure this through appsettings.json - JLavallet 2022-02-10 12:23
Configure<AbpTwilioSmsOptions>(options =>
{
options.AccountSId = "yada-yada-yada";
options.AuthToken = "yada-yada-yada";
options.FromNumber = "+18885551212";
});
// TODO - Need to configure this through appsettings.json - JLavallet 2022-02-10 12:24
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.IsMultiTenant = true;
container.UseFileSystem(fileSystem => { fileSystem.BasePath = #"D:\Product\DevFiles"; });
});
});
I modified that code to try and read from the context just like the OP. I wasn't sure what property of the context contained the configuration. I tried all kind of things and set breakpoints to try and find the configuration object in the context without success.
Configure<AbpTwilioSmsOptions>(options =>
{
options.AccountSId = context.WHAT?["AbpTwilioSms:AccountSId"];
options.AuthToken = context.WHAT?["AbpTwilioSms:AuthToken"];
options.FromNumber = context.WHAT?["AbpTwilioSms:FromNumber"];
});
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.IsMultiTenant = Convert.ToBoolean(context.WHAT?["AbpBlobStoring:IsMultiTenant"]);
container.UseFileSystem(fileSystem =>
{
fileSystem.BasePath = context.WHAT?["AbpBlobStoring:FileSystemBasePath"];
});
});
});
At that point I came across this post and found how to get the configuration object out of the context.
Not all was well, however…
For the longest time I could not understand why I could not read my appsettings.json configuration information that I had placed in the Product.HttpApi.Host root folder. I was able to get to the configuration object but my values were still null.
I then had the thought that I should add an appsettings.json file to my Product.Domain root folder; surprisingly that had no effect.
I finally came around to moving the service configuration code out of my Product.Domain.ProductDomainModule class and into my Product.HttpApi.Host.ProductHttpApiHostModule class and my Product.IdentityServer.ProductIdentityServerModule class.
[DependsOn(
typeof(ProductHttpApiModule),
typeof(AbpAutofacModule),
typeof(AbpCachingStackExchangeRedisModule),
typeof(AbpAspNetCoreMvcUiMultiTenancyModule),
typeof(AbpIdentityAspNetCoreModule),
typeof(ProductApplicationModule),
typeof(ProductEntityFrameworkCoreModule),
typeof(AbpSwashbuckleModule),
typeof(AbpAspNetCoreSerilogModule)
)]
// added by Jack
[DependsOn(typeof(AbpTwilioSmsModule))]
[DependsOn(typeof(AbpBlobStoringModule))]
[DependsOn(typeof(AbpBlobStoringFileSystemModule))]
public class ProductHttpApiHostModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
ConfigureUrls(configuration);
ConfigureConventionalControllers();
ConfigureAuthentication(context, configuration);
ConfigureSwagger(context, configuration);
ConfigureCache(configuration);
ConfigureVirtualFileSystem(context);
ConfigureDataProtection(context, configuration, hostingEnvironment);
ConfigureCors(context, configuration);
ConfigureExternalProviders(context);
ConfigureHealthChecks(context);
ConfigureTenantResolver(context, configuration);
//added by Jack
ConfigureTwilioSms(configuration);
ConfigureBlobStoring(configuration);
}
private void ConfigureBlobStoring(IConfiguration configuration)
{
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.IsMultiTenant = Convert.ToBoolean(configuration["AbpBlobStoring:IsMultiTenant"]);
container.UseFileSystem(fileSystem =>
{
fileSystem.BasePath = configuration["AbpBlobStoring:FileSystemBasePath"];
});
});
});
}
private void ConfigureTwilioSms(IConfiguration configuration)
{
Configure<AbpTwilioSmsOptions>(options =>
{
options.AccountSId = configuration["AbpTwilioSms:AccountSId"];
options.AuthToken = configuration["AbpTwilioSms:AuthToken"];
options.FromNumber = configuration["AbpTwilioSms:FromNumber"];
});
}
I then copied my configuration entries from the Product.HttpApi.Host\appsettings.json file into my Product.IdentityServer\appsettings.json file and everything worked beautifully.
{
...,
"AbpTwilioSms": {
"AccountSId": "yada-yada-yada",
"AuthToken": "yada-yada-yada",
"FromNumber": "+18885551212"
},
"AbpBlobStoring": {
"IsMultiTenant": true,
"FileSystemBasePath": "D:\\Product\\DevFiles\\"
}
}

Set a custom SessionStore for ConfigureApplicationCookie without BuildServiceProvider()

I have a .NET Core 3 project (recently upgraded from 2.2) that uses a Redis distributed cache and cookie authentication.
It currently looks something like this:
public void ConfigureServices(IServiceCollection services)
{
// Set up Redis distributed cache
services.AddStackExchangeRedisCache(...);
...
services.ConfigureApplicationCookie(options =>
{
...
// Get a service provider to get the distributed cache set up above
var cache = services.BuildServiceProvider().GetService<IDistributedCache>();
options.SessionStore = new MyCustomStore(cache, ...);
}):
}
The problem is that BuildServiceProvider() causes a build error:
Startup.cs(...): warning ASP0000: Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
This doesn't appear to be an option - ConfigureApplicationCookie is in Startup.ConfigureServices and can only configure new services, Startup.Configure can use the new services, but can't override CookieAuthenticationOptions.SessionStore to be my custom store.
I've tried adding services.AddSingleton<ITicketStore>(p => new MyCustomRedisStore(cache, ...)) before ConfigureApplicationCookie, but this is ignored.
Explicitly setting CookieAuthenticationOptions.SessionStore appears to be the only way to get it to use anything other than the local memory store.
Every example I've found online uses BuildServiceProvider();
Ideally I want to do something like:
services.ConfigureApplicationCookieStore(provider =>
{
var cache = provider.GetService<IDistributedCache>();
return new MyCustomStore(cache, ...);
});
Or
public void Configure(IApplicationBuilder app, ... IDistributedCache cache)
{
app.UseApplicationCookieStore(new MyCustomStore(cache, ...));
}
And then CookieAuthenticationOptions.SessionStore should just use whatever I've configured there.
How do I make the application cookie use an injected store?
Reference Use DI services to configure options
If all the dependencies of your custom store are injectable, then just register your store and required dependencies with the service collection and use DI services to configure options
public void ConfigureServices(IServiceCollection services) {
// Set up Redis distributed cache
services.AddStackExchangeRedisCache(...);
//register my custom store
services.AddSingleton<ITicketStore, MyCustomRedisStore>();
//...
//Use DI services to configure options
services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
.Configure<ITicketStore>((options, store) => {
options.SessionStore = store;
});
services.ConfigureApplicationCookie(options => {
//do nothing
}):
}
If not then work around what is actually registered
For example
//Use DI services to configure options
services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
.Configure<IDistributedCache>((options, cache) => {
options.SessionStore = new MyCustomRedisStore(cache, ...);
});
Note:
ConfigureApplicationCookie uses a named options instance. - #KirkLarkin
public static IServiceCollection ConfigureApplicationCookie(this IServiceCollection services, Action<CookieAuthenticationOptions> configure)
=> services.Configure(IdentityConstants.ApplicationScheme, configure);
The option would need to include the name when adding it to services.
To implement Redis Tickets in .NET Core 3.0 we did the following which is the above in a bit more of a final form::
services.AddSingleton<ITicketStore, RedisTicketStore>();
services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
.Configure<ITicketStore>((options, store) => {
options.SessionStore = store;
});
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
// ...configure identity server options
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
Here is a Redis implementation:
public class RedisTicketStore : ITicketStore
{
private const string KeyPrefix = "AuthSessionStore-";
private IDistributedCache cache;
public RedisTicketStore(IDistributedCache cache)
{
this.cache = cache;
}
public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
var guid = Guid.NewGuid();
var key = KeyPrefix + guid.ToString();
await RenewAsync(key, ticket);
return key;
}
public Task RenewAsync(string key, AuthenticationTicket ticket)
{
var options = new DistributedCacheEntryOptions();
var expiresUtc = ticket.Properties.ExpiresUtc;
if (expiresUtc.HasValue)
{
options.SetAbsoluteExpiration(expiresUtc.Value);
}
byte[] val = SerializeToBytes(ticket);
cache.Set(key, val, options);
return Task.FromResult(0);
}
public Task<AuthenticationTicket> RetrieveAsync(string key)
{
AuthenticationTicket ticket;
byte[] bytes = null;
bytes = cache.Get(key);
ticket = DeserializeFromBytes(bytes);
return Task.FromResult(ticket);
}
public Task RemoveAsync(string key)
{
cache.Remove(key);
return Task.FromResult(0);
}
private static byte[] SerializeToBytes(AuthenticationTicket source)
{
return TicketSerializer.Default.Serialize(source);
}
private static AuthenticationTicket DeserializeFromBytes(byte[] source)
{
return source == null ? null : TicketSerializer.Default.Deserialize(source);
}
}
Redis implementation from: https://mikerussellnz.github.io/.NET-Core-Auth-Ticket-Redis/

MassTransit 5.2, SignalR: How can i get the IHubContext inside my Consumer?

My main problem is to get the right instance of the SignalR hub.
Context: Im building a webapplication which communicates with a couple of external systems. CRUD operations in my application result in updating the databases of the external systems.
In this example i have 3 services running:
ExternalSystem | StateMachine | .NET CORE WebAPI
When i post the 'create employee' form, a RabbitMQ message will be sent from the WebAPI to the statemachine. The statemachine then sends a couple of create messages to my external system service which updates the database. Thereafter, it updates the statemachine to keep track of the createoperation.
Form -> API -> StateMachine -> ExternalSystem -> StateMachine -> API
So far so good. Now i would like to use SignalR to send the status updates to the client. So i've implemented this consumer in the API:
public class UpdatesConsumer :
IConsumer<IExternalSystemUpdateMessage>
{
private readonly IHubContext<UpdatesHub> _updaterHubContext;
public UpdatesConsumer(IHubContext<UpdatesHub> hubContext)
{
_updaterHubContext = hubContext;
}
public Task Consume(ConsumeContext<IExternalSystemUpdateMessage> context)
{
//return _updaterHubContext.Clients.Group(context.Message.CorrelationId.ToString()).SendAsync("SEND_UPDATE", context.Message.Message);// this.SendUpdate(context.Message.CorrelationId, context.Message.Message);
return _updaterHubContext.Clients.All.SendAsync("SEND_UPDATE", context.Message.Message);
}
}
This is my SignalR hub:
public class UpdatesHub :
Hub
{
public Task SendUpdate(Guid correlationId, string message)
{
return Clients.Group(correlationId.ToString()).SendAsync("SEND_UPDATE", message);
}
}
And this is how the Bus and consumer is instantiated:
public void ConfigureServices(IServiceCollection services)
{
_services = services;
services.AddMvc();
services.AddSignalR();
//services.AddSingleton<IHubContext<UpdatesHub>>();
WebAPI.CreateBus();
}
public static IServiceCollection _services;
static IBusControl _busControl;
public static IBusControl Bus
{
get
{
return _busControl;
}
}
public static void CreateBus()
{
IRMQConnection rmqSettings = Config.GetRMQConnectionConfig("rmq-settings.json", "connection");
_busControl = MassTransit.Bus.Factory.CreateUsingRabbitMq(x =>
{
var host = x.Host(BusInitializer.GetUri("", rmqSettings), h =>
{
h.Username(rmqSettings.UserName);
h.Password(rmqSettings.Password);
});
x.ReceiveEndpoint(host, "externalsystems.update",
e => { e.Consumer(() => new UpdatesConsumer((IHubContext<UpdatesHub>)Startup.__serviceProvider.GetService(typeof(IHubContext<UpdatesHub>)))); });
});
TaskUtil.Await(() => _busControl.StartAsync());
}
=========================================================================
So the problem is that _updaterHubContext.Clients in my Consumer class, always turn out to be empty. I've tested accessing the hub in a controller, and the clients do show up:
public class TestController : Controller
{
private readonly IHubContext<UpdatesHub> _hubContext;
public TestController(IHubContext<UpdatesHub> hubContext)
{
_hubContext = hubContext;
}
[HttpGet]
[Route("api/Test/")]
public IActionResult Index()
{
return View();
}
}
How can i get the right instance of the hub in my Consumer class? Or how can i access the IServiceCollection that .net is using?
Thnx in advance!
You can register your consumer so that MassTransit will resolve it from the IServiceProvider using the support provided in the MassTransit.Extensions.DependencyInjection package.
x.ReceiveEndpoint(host, "externalsystems.update", e =>
{
e.Consumer<UpdatesConsumer>(_serviceProvider);
});
Be sure to register your UpdatesConsumer in the container as well. This should resolve a new instance of the consumer for each message received on the endpoint.
Why not register Bus using Microsoft Dependency Injection. It should fix your issue, it will Resolve your consumer using IServiceProvider

Set dummy IP address in integration test with Asp.Net Core TestServer

I have a C# Asp.Net Core (1.x) project, implementing a web REST API, and its related integration test project, where before any test there's a setup similar to:
// ...
IWebHostBuilder webHostBuilder = GetWebHostBuilderSimilarToRealOne()
.UseStartup<MyTestStartup>();
TestServer server = new TestServer(webHostBuilder);
server.BaseAddress = new Uri("http://localhost:5000");
HttpClient client = server.CreateClient();
// ...
During tests, the client is used to send HTTP requests to web API (the system under test) and retrieve responses.
Within actual system under test there's some component extracting sender IP address from each request, as in:
HttpContext httpContext = ReceiveHttpContextDuringAuthentication();
// edge cases omitted for brevity
string remoteIpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString()
Now during integration tests this bit of code fails to find an IP address, as RemoteIpAddress is always null.
Is there a way to set that to some known value from within test code? I searched here on SO but could not find anything similar. TA
You can write middleware to set custom IP Address since this property is writable:
public class FakeRemoteIpAddressMiddleware
{
private readonly RequestDelegate next;
private readonly IPAddress fakeIpAddress = IPAddress.Parse("127.168.1.32");
public FakeRemoteIpAddressMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
httpContext.Connection.RemoteIpAddress = fakeIpAddress;
await this.next(httpContext);
}
}
Then you can create StartupStub class like this:
public class StartupStub : Startup
{
public StartupStub(IConfiguration configuration) : base(configuration)
{
}
public override void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
base.Configure(app, env);
}
}
And use it to create a TestServer:
new TestServer(new WebHostBuilder().UseStartup<StartupStub>());
As per this answer in ASP.NET Core, is there any way to set up middleware from Program.cs?
It's also possible to configure the middleware from ConfigureServices, which allows you to create a custom WebApplicationFactory without the need for a StartupStub class:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost
.CreateDefaultBuilder<Startup>(new string[0])
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}
public class CustomStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return app =>
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
next(app);
};
}
}
Using WebHost.CreateDefaultBuilder can mess up with your app configuration.
And there's no need to change Product code just to accommodate for testing, unless absolutely necessary.
The simplest way to add your own middleware, without overriding Startup class methods, is to add the middleware through a IStartupFilter‍ as suggested by Elliott's answer.
But instead of using WebHost.CreateDefaultBuilder, just use
base.CreateWebHostBuilder().ConfigureServices...
public class CustomWAF : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return base.CreateWebHostBuilder().ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}
I used Elliott's answer within an ASP.NET Core 2.2 project. However, updating to ASP.NET 5.0, I had to replace the override of CreateWebHostBuilder with the below override of CreateHostBuilder:
protected override IHostBuilder CreateHostBuilder()
{
return Host
.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}

Nservicebus 5 and later Web Api Depenedency Injection settings

How can I configure Web api dependency settings for NserviceBus 5 and later version.
Version 3 or 4 is like this:
public static class ConfigureWebApi
{
public static Configure ForWebApi(this Configure configure)
{
// Register our http controller activator with NSB
configure.Configurer.RegisterSingleton(typeof(IHttpControllerActivator),
new NSBHttpControllerActivator());
// Find every http controller class so that we can register it
var controllers = Configure.TypesToScan
.Where(t => typeof(IHttpController).IsAssignableFrom(t));
// Register each http controller class with the NServiceBus container
foreach (Type type in controllers)
configure.Configurer.ConfigureComponent(type, ComponentCallModelEnum.Singlecall);
// Set the WebApi dependency resolver to use our resolver
GlobalConfiguration.Configuration.ServiceResolver.SetResolver(new NServiceBusResolverAdapter(configure.Builder));
// Required by the fluent configuration semantics
return configure;
}
}
But Version 5 does not use Configure class, that use BusConfiguration
I try this but can not scan assemblies:
public static class ConfigureWebApi
{
public static BusConfiguration ForWebApi(this BusConfiguration configuration)
{
configuration.RegisterComponents(c => c.RegisterSingleton(typeof(IHttpControllerActivator),
new NServiceBusHttpControllerActivator()));
????
}
}
I'm not sure which way you're thinking. I'm asking, because I might be wrong with my answer. If so, let me know and I'll try to update it.
The way I go about this issue is setting up the container first and then have NServiceBus use that container. I'm using AutoFac and create a special class to set it up.
Disclaimer : I'm copying this from an existing app and didn't try nor compile it. I'm 100% sure this is working though, although I might've forgotten a line or added one too much! :)
public class DependenciesConfig
{
public static IContainer RegisterDependencies()
{
ContainerBuilder builder = new ContainerBuilder();
// MVC Controllers
builder.RegisterModule(new AutofacWebTypesModule());
builder.RegisterControllers(Assembly.GetExecutingAssembly())
// WebAPI controllers
var config = GlobalConfiguration.Configuration;
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterWebApiFilterProvider(config);
// Way more registrations
// Next line is AutoFac specific for WebAPI
builder.RegisterFilterProvider();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
return container;
}
}
Then I have an additional class for registering NServiceBus. I don't have handlers in my web app, nor do I publish messages, so I use a SendOnly endpoint.
public class ServiceBus
{
public static ISendOnlyBus Bus { get; private set; }
private static readonly object padlock = new object();
public static void Init(ILifetimeScope container)
{
if (Bus != null) return;
NServiceBus.Logging.LogManager.Use<CommonLoggingFactory>();
lock (padlock)
{
if (Bus != null) return;
var configuration = new BusConfiguration();
configuration.UseSerialization<JsonSerializer>();
configuration.UseContainer<AutofacBuilder>(x => x.ExistingLifetimeScope(container));
configuration.UseTransport<AzureStorageQueueTransport>();
ConventionsBuilder conventions = configuration.Conventions();
conventions.DefiningCommandsAs(t => t.Namespace != null && t.Namespace.StartsWith("Messages") && t.Namespace.EndsWith("Commands"));
Bus = NServiceBus.Bus.CreateSendOnly(configuration);
}
}
}
Is this what you're looking for?