I believe I am missing/misunderstanding something fundamental about the way .net5 works. In setting up an integration test environment for my GraphQL API, I am missing the step on how to start the GraphQL server from said test environment.
When I run the main project, the server is started properly and I can navigate to localhost in the browser and successfully execute GraphQL queries/mutations. My goal here is to set up some automated integration tests.
I'm using NUnit as my test runner and am using WebApplicationFactory<Startup> to "start the server" as I understand it.
In my test project, I'm under the impression that WebApplicationFactory<Startup> is supposed to basically use the Startup.cs class from my main project in my test project so that I don't have to duplicate all the settings, configurations, and injected services. Please correct me if that assumption is not correct.
I've pasted the code I think is relevant.
ApiWebApplicationFactory<Startup>
public class ApiWebApplicationFactory<TStartup> : WebApplicationFactory<Startup> where TStartup : class
{
public static IConfigurationRoot Configuration { get; set; }
public ApiWebApplicationFactory()
{
var configBuilder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
Configuration = configBuilder.Build();
}
protected override void ConfigureClient(HttpClient client)
{
base.ConfigureClient(client);
client.BaseAddress = new Uri("https://localhost");
client.Timeout = new TimeSpan(0, 10, 0);
}
// Based on my assumption this class reuses everything in the Startup.cs class
// I don't actually think this is necessary, but thought it was worth trying
// the test with and without this code.
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services
.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.AddType<GraphQLContentItem>()
.AddType<GraphQLFolder>();
});
}
}
OneTimesetUp
[OneTimeSetUp]
public void OneTimeSetUp()
{
_factory = new ApiWebApplicationFactory<Startup>();
_client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
services.AddScoped<ICacheRepository, MockCache>();
});
}).CreateClient();
var connString = ApiWebApplicationFactory<Startup>.Configuration.GetConnectionString("DefaultConnection");
var options = new DbContextOptionsBuilder<CmsContext>()
.UseMySql(connString, ServerVersion.AutoDetect(connString))
.Options;
_dbContext = new CmsContext(options);
_dbContext.Database.EnsureCreated();
}
Test
[Test]
public async Task Test()
{
// If I set a breakpoint here, I can't navigate to the URL like I'm expecting to
var graphQLHttpClient =
new GraphQLHttpClient(
new GraphQLHttpClientOptions { EndPoint = new Uri("https://localhost/graphql") },
new NewtonsoftJsonSerializer(),
_client);
var request = new GraphQLRequest
{
Query = #"
query GetCurrentSession() {
getCurrentSession() {
id
name
}
}",
OperationName = "GetCurrentSession"
};
// Error is thrown here with "Bad Request"
var response = await graphQLHttpClient.SendQueryAsync<Session>(request);
// Further code is omitted
}
Please let me know if you see what I am missing. Thanks in advance~
Related
I use this same method to set the eonvironmentname for a web app that I publish to a folder. However, when publishing a .net core console app that is using IWebhostBuilder, the environmentname isnt seeming to get updated?
I have a .net core console app that I am publishing to a folder location and the console app uses IWebhostBuilder, hence I need to be able to set the environment name prior to publishing. It seems to want to grab the default appsettings, rather than the one for my target environment (Dev). I have a appsettings.Dev.json file
My publish profile looks like this
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Dev</Configuration>
<Platform>Any CPU</Platform>
<TargetFramework>netcoreapp2.2</TargetFramework>
<PublishDir>\\dev-server1\d$\Services\BoxService</PublishDir>
<SelfContained>false</SelfContained>
<_IsPortable>true</_IsPortable>
<EnvironmentName>Dev</EnvironmentName>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
</Project>
[ Edit ]
Nothing was working, so I did an experiment because obviously I am missing something or just dont understand. I created a new test console app with nothing in it except one class with a Main method. Added the necessary packages (Extensions.Configuration etc), then proceeded to publish the app to a local folder with the following in my publishprofile.
<EnvironmentName>Local</EnvironmentName>
This is obviously not being used, because when I run it from visual studio, it reports the correct environment has been set, but when run from the published folder, its as if the environment has not been set.
Here is Program.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.EnvironmentVariables;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Cmd1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var environmentName =
Environment.GetEnvironmentVariable("ENVIRONMENT");
// create service collection
var services = new ServiceCollection();
// build config
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false)
.AddJsonFile($"appsettings.{environmentName}.json", true)
.AddEnvironmentVariables()
.Build();
// setup config
services.AddOptions();
services.Configure<AppSettings>(configuration.GetSection("App"));
// create service provider
var serviceProvider = services.BuildServiceProvider();
var appSettings = serviceProvider.GetService<IOptions<AppSettings>>();
string env = Environment.GetEnvironmentVariable("Environment");
Console.WriteLine($" We are looking at {appSettings.Value.TempDirectory} from environment: {env}");
}
}
}
Appsettings.json
{
"App": {
"TempDirectory": "d:\temp"
}
}
Appsettings.Local.json
{
"App": {
"TempDirectory": "c:\\temp\\rss-hero\\tmp\\"
}
}
Setting Environment in VS
Results, from VS
Results when run from published folder
You need to configure IWebhostBuilder like this
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureAppConfiguration(builder =>
{
builder.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.Dev.json", optional: true) // add more if you have
.AddEnvironmentVariables();
});
Then depending on the environment it will use appsettings{envrionment}.json. If no other present it will use appsettings.json (default). After publishing you can check the web.config to check the environment name it's using.
EDIT
For console app you do something like this
private static IConfigurationBuilder Configure(IConfigurationBuilder config, string environmentName)
{
return config
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{environmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
}
And use like this (.Net core generic host)
Host.CreateDefaultBuilder(args)
.ConfigureHostConfiguration(builder =>
{
Configure(builder, "Dev");
})
.UseConsoleLifetime()
Or if you want to get the environment from System environment variables.
public static IConfiguration CreateConfiguration()
{
var env = new HostingEnvironment
{
EnvironmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"),
ApplicationName = AppDomain.CurrentDomain.FriendlyName,
ContentRootPath = AppDomain.CurrentDomain.BaseDirectory,
ContentRootFileProvider = new PhysicalFileProvider(AppDomain.CurrentDomain.BaseDirectory)
};
var config = new ConfigurationBuilder();
var configured = Configure(config, env);
return configured.Build();
}
I'm using the following code to send a request/response message between two different processes.
This is the process that "sends" the request:
// configure host
var hostUri = new Uri(configuration["RabbitMQ:Host"]);
services.AddSingleton(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(hostUri, h =>
{
h.Username(configuration["RabbitMQ:Username"]);
h.Password(configuration["RabbitMQ:Password"]);
});
}));
// add request client
services.AddScoped(provider => provider.GetRequiredService<IBus>().CreateRequestClient<QueryUserInRole, QueryUserInRoleResult>(new Uri(hostUri, "focus-authorization"), TimeSpan.FromSeconds(5)));
// add dependencies
services.AddSingleton<IPublishEndpoint>(provider => provider.GetRequiredService<IBusControl>());
services.AddSingleton<ISendEndpointProvider>(provider => provider.GetRequiredService<IBusControl>());
services.AddSingleton<IBus>(provider => provider.GetRequiredService<IBusControl>());
// add the service class so that the runtime can automatically handle the start and stop of our bus
services.AddSingleton<Microsoft.Extensions.Hosting.IHostedService, BusService>();
Here's the implementation of the BusService:
public class BusService : Microsoft.Extensions.Hosting.IHostedService
{
private readonly IBusControl _busControl;
public BusService(IBusControl busControl)
{
_busControl = busControl;
}
public Task StartAsync(CancellationToken cancellationToken)
{
return _busControl.StartAsync(cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _busControl.StopAsync(cancellationToken);
}
}
The problem is that when the CreateRequestClient code runs, the bus has not started up yet. Thus the response is never returned from the consumer.
If I replace the host configuration with the following code, I get the desired behavior:
var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(hostUri, h =>
{
h.Username(configuration["RabbitMQ:Username"]);
h.Password(configuration["RabbitMQ:Password"]);
});
});
bus.Start();
services.AddSingleton(bus);
For some reason, the BusService(IHostedService) executes AFTER the AddScoped delegates.
My question is: what is the correct way to start up the bus before using the CreateRequestClient method? Or is the latter approach to starting up the bus sufficient?
I have declared connection string in appsetting.json file. I would like to change the connection string through web api call.
Is there a way to do it?
I am working with ASP.Net Core.
appsettings.json:
"Database": {
"ProviderName": "MySQL",
"ConnectionString": "server=localhost;database=sampledb;uid=user;pwd=user"
},
Startup.cs
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
//Accessing Database section from appsettings.json
services.Configure<dbSettings>(StaticSettings.GetSection("Database"));
Firstly, in development environment the right place to store connection string is in User Secrets. In Visual Studio, Right click on Project > Manage User Secrets, opens secrets.json. User Secrets option works only on your dev. machine.
While if your app is running on Azure, Azure Portal has application settings options to define connection strings for your app.
Coming to your question, you can store multiple connection strings in application settings. For example:
"ConnectionStrings": {
"dev_db": "Server=devSQLServerName;Database=dev;User Id=userName;Password=yourPassword",
"test_db": "Server=testSQLServerName;Database=test;User Id=userName;Password=yourPassword",
"staging_db": "Server=stagingSQLServerName;Database=staging;User Id=userName;Password=yourPassword",
"production_db": "Server=prodSQLServerName;Database=staging;User Id=userName;Password=yourPassword"
}
Now, write a method to get the connection string based on your environment.
public string GetDBConnectionString(string environment)
{
string connectionString = string.Empty;
if (environment == "production")
{
connectionString = Configuration["ConnectionStrings:production_db"];
}
else if (environment == "development")
{
connectionString = Configuration["ConnectionStrings:dev_db"];
}
else if (environment == "test")
{
connectionString = Configuration["ConnectionStrings:test_db"];
}
else if (environment == "stage")
{
connectionString = Configuration["ConnectionStrings:staging_db"];
}
if (connectionString == null)
{
throw new Exception("Could not locate production DB connection string for env: " + environment );
}
else
{
return connectionString;
}
}
Now call this method from your Web API's action method or where ever you want to consume.
Trying to run core api host. i have this in the EntityFrameworkModule
public override void PreInitialize()
{
Configuration.MultiTenancy.IsEnabled = true;`
// CONNECTION STRING RESOLVER
Configuration.ReplaceService<IConnectionStringResolver, DbPerTenantConnectionStringResolver>(DependencyLifeStyle.Transient);
Configuration.DefaultNameOrConnectionString = MyConsts.DefaultConnectionStringName;
if (!SkipDbContextRegistration)
{
//DEFAULT
Configuration.Modules.AbpEfCore().AddDbContext<MyContext>(options =>
{
if (options.ExistingConnection != null)
MyContextConfigurer.Configure(options.DbContextOptions, options.ExistingConnection);
else
MyContextConfigurer.Configure(options.DbContextOptions, options.ConnectionString);
});
}
}
ERROR
Mvc.ExceptionHandling.AbpExceptionFilter - Can't create component 'Ccre.EntityFrameworkCore.AbpZeroDbMigrator' as it has dependencies to be satisfied.
'Ccre.EntityFrameworkCore.AbpZeroDbMigrator' is waiting for the following dependencies:
- Service 'Abp.MultiTenancy.IDbPerTenantConnectionStringResolver' which was not registered.
How/where do I register the IDbPerTenantConnectionStringResolver?
I have this line in the PreInitialize of the Migrator.MigratorModule
Configuration.ReplaceService<IConnectionStringResolver, DbPerTenantConnectionStringResolver>(DependencyLifeStyle.Transient);
as well as in the EntityFrameworkModule
I am trying to implement a heartbeat feature for my application hence was trying to implement recurring message feature from masstransit rabbitmq. I was trying to implement it on the sample given on masstransit's website. Here's all of the code.
namespace MasstransitBasicSample
{
using System;
using System.Threading.Tasks;
using MassTransit;
using MassTransit.Scheduling;
class Program
{
static void Main(string[] args)
{
var bus = Bus.Factory.CreateUsingRabbitMq(sbc =>
{
var host = sbc.Host(new Uri("rabbitmq://localhost"), h =>
{
h.Username("guest");
h.Password("guest");
});
sbc.UseMessageScheduler(new Uri("rabbitmq://localhost/quartz"));
sbc.ReceiveEndpoint(host, "test_queue", ep =>
{
ep.Handler<YourMessage>(context =>
{
return Console.Out.WriteLineAsync($"Received: {context.Message.Text}");
});
ep.Handler<PollExternalSystem>(context =>
{
return Console.Out.WriteLineAsync($"Received: {context.Message}");
});
});
});
bus.Start();
SetRecurring(bus);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
bus.Stop();
}
private static async Task SetRecurring(IBusControl bus)
{
var schedulerEndpoint = await bus.GetSendEndpoint(new Uri("rabbitmq://localhost/quartz"));
var scheduledRecurringMessage = await schedulerEndpoint.ScheduleRecurringSend(
new Uri("rabbitmq://localhost/test_queue"), new PollExternalSystemSchedule(), new PollExternalSystem());
}
}
public class YourMessage { public string Text { get; set; } }
public class PollExternalSystemSchedule : DefaultRecurringSchedule
{
public PollExternalSystemSchedule()
{
CronExpression = "* * * * *"; // this means every minute
}
}
public class PollExternalSystem { }
}
I have created a queue called quartz in my rabbitmq queue.
When i run the application it sends a message to the quartz queue and that message just stays there , it does not go to the test queue.
I was also expecting another message to be sent to the quartz queue after a minute based on the cron expression, that also does not happen.
Is my setup wrong?
Any help would be much appreciated.
You need to run the scheduling service that listens on rabbitmq://localhost/quartz, where your messages are being sent.
The documentation page says:
There is a standalone MassTransit service, MassTransit.QuartzService,
which can be installed and used on servers for this purpose. It is
configured via the App.config file and is a good example of how to
build a standalone MassTransit service.
Alternatively, you can host Quartz scheduling in the same process by using in-memory scheduling, described here, by configuring it like this:
var busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://localhost/"), h =>
{
h.Username("guest");
h.Password("guest");
});
cfg.UseInMemoryScheduler();
});