I am working on a prototype app. Where we are using the new Blazor WebAssembly. What I am trying to do is access my JSON file to get access to endpoints we have setup to make API calls. This is what I have currently
appsettings.json:
"Endpoints": {
"AuthEndpoint": "https://auth"
}
Program.cs:
public static async Task Main(string[] args)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
//var endpointSettings = new EndpointSettings()
//{
// AuthEndpoint = config["Endpoints:AuthEndpoint"]
//};
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) })
.AddScoped<IAuthenticationService, AuthenticationService>()
.AddTransient(_ =>
{
return config.GetSection("Endpoints")
.Get<EndpointSettings>();
})
.AddTransient<IAuthClient, AuthClient>();
await builder.Build().RunAsync();
}
When it builds in the console log I am getting this error message
I am trying to find out why this happening and what can I do to fix it
something along these lines for example:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args)
.Build()
.Run();
}
public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
BlazorWebAssemblyHost.CreateDefaultBuilder()
.UseBlazorStartup<Startup>();
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(GetConfiguration());
}
public void Configure(IComponentsApplicationBuilder app )
{
app.AddComponent<App>("app");
}
public APISetting GetConfiguration()
{
// Get the configuration from embedded dll.
using (var stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("appsettings.json"))
using (var reader = new System.IO.StreamReader(stream))
{
return System.Text.Json.JsonSerializer.Deserialize<APISetting>(reader.ReadToEnd());
}
}
}
where APISetting class "matches" your appsettings.json:
public class APISetting
{
public string AuthEndpoint { get; set; }
...
...
}
and appsettings.json "build action" is set as "Embedded resource"
Related
If I do not use Autofac, I can connect without issues. But when I use Autofac, I get an error of
java.util.concurrent.ExecutionException: java.net.ConnectException: Connection refused`.
Here is how I setup my SignalR Server:
public class Program
{
private static ApplicationSetting mApplicationSetting;
private static ILoggingOperation mLoggingOperation;
static void Main(string[] args)
{
StartWebHost();
Console.ReadLine();
}
static void StartWebHost()
{
using (WebApp.Start("http://10.16.32.52:8085"))
{
Console.WriteLine(#"Server running at 10.16.32.52:8085");
}
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
var builder = new ContainerBuilder();
builder.RegisterHubs(Assembly.GetExecutingAssembly()).PropertiesAutowired();
builder.RegisterType<Application>().As<IApplication>();
builder.RegisterType<CommonActions>().As<ICommonActions>();
builder.RegisterType<LoggingOperation>().As<ILoggingOperation>();
var container = builder.Build();
var resolver = new Autofac.Integration.SignalR.AutofacDependencyResolver(container);
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR(new HubConfiguration
{
Resolver = resolver,
EnableJSONP = true,
EnableDetailedErrors = true,
EnableJavaScriptProxies = true
});
}
}
My Hub is called ApplicationHub and it is in another class. What can I try next?
Based on that link I'm trying to create EasyNetQ Dispatcher for my messages. For some reason my Consumer is not triggered when my message appears in queue and I've no idea what the reason might be.
public class Program
{
static void Main(string[] args)
{
var config = LoadConfiguration();
ConfigureServices(config);
Console.ReadLine();
}
public static IConfiguration LoadConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
return builder.Build();
}
private static void ConfigureServices(IConfiguration config)
{
var services = new ServiceCollection()
.AddSingleton(config)
.AddEasyNetQ("host=127.0.0.1:5672;username=guest;password=guest")
.AddSingleton<AutoSubscriber>(provider =>
{
// When I put breakpoint below - is never reached. Is that correct behavior?
var subscriber = new AutoSubscriber(provider.GetRequiredService<IBus>(), "SomePrefix")
{
AutoSubscriberMessageDispatcher = provider.GetRequiredService<IAutoSubscriberMessageDispatcher>()
};
subscriber.Subscribe(new Assembly[] { Assembly.GetExecutingAssembly() });
subscriber.SubscribeAsync(new Assembly[] { Assembly.GetExecutingAssembly() });
return subscriber;
});
services.BuildServiceProvider();
}
}
Below is the rest of nested code, though it seems working fine - so probably the problem is in Program.cs
EasyNetQExtension
public static class EasyNetQExtension
{
private static void InternalInitEasyNetQ(IServiceCollection service, string rabbitMqConnection)
{
service.AddSingleton(RabbitHutch.CreateBus(rabbitMqConnection));
service.AddSingleton<IAutoSubscriberMessageDispatcher, ConsumerMessageDispatcher>(serviceProvider => new ConsumerMessageDispatcher(serviceProvider));
var consumerTypes = Assembly.GetExecutingAssembly().GetTypes()
.Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface)
.Where(x => x.GetInterfaces().Any(t => t.Name == typeof(IConsume<>).Name));
foreach (var consumerType in consumerTypes)
{
service.AddTransient(consumerType);
}
// My consumer is found here, so this works properly
var consumerAsyncTypes = Assembly.GetExecutingAssembly().GetTypes()
.Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface)
.Where(x => x.GetInterfaces().Any(t => t.Name == typeof(IConsumeAsync<>).Name));
foreach (var consumerAsyncType in consumerAsyncTypes)
{
service.AddTransient(consumerAsyncType);
}
}
public static IServiceCollection AddEasyNetQ(this IServiceCollection service, string rabbitMqConnectionString)
{
InternalInitEasyNetQ(service, rabbitMqConnectionString);
return service;
}
}
ConsumerMessageDispatcher
public class ConsumerMessageDispatcher : IAutoSubscriberMessageDispatcher
{
private readonly IServiceProvider _serviceProvider;
public ConsumerMessageDispatcher(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Dispatch<TMessage, TConsumer>(TMessage message, CancellationToken cancellationToken = new CancellationToken()) where TMessage : class where TConsumer : class, IConsume<TMessage>
{
try
{
TConsumer consumer = _serviceProvider.GetRequiredService<TConsumer>();
consumer.Consume(message);
}
catch (Exception exception)
{
throw;
}
}
public async Task DispatchAsync<TMessage, TConsumer>(TMessage message, CancellationToken cancellationToken = new CancellationToken()) where TMessage : class where TConsumer : class, IConsumeAsync<TMessage>
{
try
{
TConsumer consumer = _serviceProvider.GetRequiredService<TConsumer>();
await consumer.ConsumeAsync(message);
}
catch (Exception exception)
{
throw;
}
}
}
First, you have to implement IConsumer<> interface in your console app, so that it can be registered via InternalInitEasyNetQ method.
using EasyNetQ.AutoSubscribe;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
namespace MyApp
{
public class ConsumeTextMessage : IConsume<string>
{
private readonly ILogger _logger;
public ConsumeTextMessage(ILogger<ConsumeTextMessage> logger)
{
_logger = logger;
}
public void Consume(string message, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Logging the message: " + message);
Console.WriteLine("Reading the message: " + message);
}
}
}
Second, you are missing part where you write message using IAutoSubscriberMessageDispatcher. You can resolve IAutoSubscriberMessageDispatcher interface using IServiceProvider or dependency injection. Something along those lines:
var dispatcher = _provider.GetRequiredService<IAutoSubscriberMessageDispatcher>();
dispatcher.Dispatch<string, ConsumeTextMessage>("Dispatch my message - " + DateTime.Now);
I fixed that by moving Autosubscriber to separate method so the correct looks:
public class Program
{
static void Main(string[] args)
{
var config = LoadConfiguration();
var provider = ConfigureServices(config);
ConfigureConsumers(provider);
Console.ReadLine();
}
public static IConfiguration LoadConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
return builder.Build();
}
private static ServiceProvider ConfigureServices(IConfiguration configuration)
{
var services = new ServiceCollection()
.AddTransient<IEmailSender, EmailSender>()
.Configure<AuthMessageSenderOptions>(options => configuration.GetSection("SendGridEmailSettings").Bind(options))
.AddEasyNetQ(configuration["QueueConnectionData"]);
return services.BuildServiceProvider();
}
private static void ConfigureConsumers(ServiceProvider provider)
{
var autoSubscriber = new AutoSubscriber(provider.GetRequiredService<IBus>(), "SomePrefix")
{
AutoSubscriberMessageDispatcher = provider.GetRequiredService<IAutoSubscriberMessageDispatcher>()
};
autoSubscriber.Subscribe(new[] { Assembly.GetExecutingAssembly() });
autoSubscriber.SubscribeAsync(new[] { Assembly.GetExecutingAssembly() });
}
}
I have a project .Net Core in React and, to save time, I would like to test a method to export files by a Console Application. But I can't to implement the DI in my console, here's the error:
Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger`1[MyPrj.Managers.Managers.Implementations.MyLogManager]' while attempting to activate 'MyPrj.Managers.Managers.Implementations.MyLogManager'.
This is a simplified version of my Program.cs:
class Program
{
public static IConfigurationRoot Configuration;
public static ServiceProvider serviceProvider;
public static void Main(string[] args)
{
var services = new ServiceCollection();
var builder = new ConfigurationBuilder()
.SetBasePath(Path.Combine(AppContext.BaseDirectory))
.AddJsonFile("appsettings.json", optional: true);
Configuration = builder.Build();
services.AddDbContext<MyDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.Configure<MailOptions>(Configuration.GetSection("EmailSettings"));
[... other options...]
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<ILogManager, MyLogManager>();
services.AddScoped<IMailManager, MailManager>();
[... other services ...]
services.AddScoped<IDocumentationManager, DocumentationManager>();
serviceProvider = services.BuildServiceProvider();
MainAsync().GetAwaiter().GetResult();
Console.WriteLine("END");
Console.ReadKey();
}
private static async Task MainAsync()
{
await ExportAsync();
}
public static async Task ExportAsync()
{
using (MyDBContext dbContext = serviceProvider.GetService<MyDBContext>())
{
List<User> user = dbContext.Users.ToList();
var logger = serviceProvider.GetService<ILogManager>();
var docManager = serviceProvider.GetService<IDocumentationManager>();
string userAnswer = Console.ReadLine();
var file = await docManager.GetUser(userAnswer);
File.WriteAllBytes("C:\tmp\user.txt", file.File);
}
}
}
Since you are creating your console app from scratch you have to manually configure the logger, otherwise it would not be injected.
var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
.AddConsole()
.AddEventLog();
});
ILogger logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("Example log message");
services.AddSingleton<ILoggerFactory>(loggerFactory);
public class MyLogManager : ILogManager {
private readonly ILogger<MyLogManager> logger;
public MyLogManager(ILoggerFactory factory) {
logger = factory.CreateLogger<MyLogManager>();
}
}
you can find more here https://learn.microsoft.com/aspnet/core/fundamentals/logging/?view=aspnetcore-3.1
You can use a HostedService for encapsulating the StartAsync and StopAsync, making the code more elegancy.
public class HostedService : IHostedService
{
private readonly ISolrSeeder _seeder;
public HostedService(ISolrSeeder seeder)
{
_seeder = seeder;
}
public async Task StartAsync(CancellationToken cancellationToken)
=> await _seeder.SeedAsync(cancellationToken);
public async Task StopAsync(CancellationToken cancellationToken) =>
await Task.Run(() => Environment.Exit(Environment.ExitCode), cancellationToken);
}
And then, you can make your program.cs represents the startup from a console application, considering many environments.
internal class Program
{
private const string SettingsName = "appsettings";
private const string SettingsExtention = ".json";
private const string AppSettings = SettingsName + SettingsExtention;
public static async Task Main(string[] args)
=> await new HostBuilder()
.ConfigureHostConfiguration(configHost =>
{
configHost
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(AppSettings, true, true)
.AddEnvironmentVariables()
.AddCommandLine(args);
})
.ConfigureAppConfiguration((hostContext, configApp) =>
{
configApp
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(AppSettings, true, true)
.AddJsonFile(SettingsName + "." + hostContext.HostingEnvironment.EnvironmentName + SettingsExtention, true, true)
.AddEnvironmentVariables()
.AddCommandLine(args);
})
.ConfigureServices((hostContext, services) =>
{
services
.AddLogging()
.AddRepositories()
.AddDbContext(options => options.ConnectionString = hostContext.Configuration.GetConnectionString("DefaultConnection"))
.AddSolr(options =>
{
options.BaseAddress = hostContext.Configuration["Solr:BaseAddress"];
options.Core = hostContext.Configuration["Solr:Core"];
})
.AddScoped<ISolrSeeder, SolrSeeder>()
.AddHostedService<HostedService>();
})
.ConfigureLogging((hostContext, configLogging) =>
{
configLogging.AddConsole();
})
.UseConsoleLifetime()
.Build()
.RunAsync();
}
The rest of the code is available on this repository.
Thanks to all,
it has been enough adding services.AddLogging(), following #Steven 's advice, to solve my problem.
I am trying to break out reusable code for the APIs that I have to create, but there is one issue that has had me stumped for a few days now. When all of the code resides in a single project, WebAPI and the common code exists within a folder called Core, everything works.
Now, if I take the code within Core and put it in its own project WebAPI.Core, the API starts, Swagger loads, but every request produces 404. I have narrowed down the problem, and it only occurs when Startup and Program are in different projects. Is that the intended behavior, or did I do something wrong?
Here is the Startup which is located in the WebAPI.Core project:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddApiVersioning(options =>
{
options.UseApiBehavior = false;
options.ReportApiVersions = true;
options.ApiVersionReader = new HeaderApiVersionReader("api-version");
options.AssumeDefaultVersionWhenUnspecified = true;
});
services.AddMvc(options => { options.EnableEndpointRouting = false; });
services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
services
.AddControllers(options => options.Filters.Add(new ErrorFilter()))
.AddJsonOptions(options => options.JsonSerializerOptions.IgnoreNullValues = true);
}
public void Configure(IApplicationBuilder app)
{
const string swagger = "Swagger";
app.UseMvc(routes => routes.MapRoute("default", "/{controller}/{action}/{*id}", new {id = string.Empty}));
app.UseRouting();
app.UseEndpoints(builder => builder.MapControllers());
var directory = Combine(Api.CurrentDirectory, swagger);
if (Exists(directory))
{
Delete(directory, true);
}
CreateDirectory(directory);
app.UseDefaultFiles().UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(directory),
RequestPath = $"/{swagger}"
});
Api.Assembly.CreateDocuments(AppSettings["Title"], AppSettings["Description"], AppSettings["Name"], AppSettings["Email"], directory);
app.UseSwaggerUI(setupAction =>
{
setupAction.RoutePrefix = string.Empty;
foreach (var file in GetFiles(directory))
{
var fileName = GetFileNameWithoutExtension(file);
setupAction.SwaggerEndpoint($"/{swagger}/{fileName}.json", fileName.Replace("-", " "));
}
});
}
}
Here is Api, the link between Startup and Program, also located in WebAPI.Core:
public static class Api
{
public static void Run<TStartup>(this Assembly assembly, string[] args) where TStartup : Startup
{
Assembly = assembly;
CurrentDirectory = GetDirectoryName(UnescapeDataString(new UriBuilder(assembly.CodeBase).Path));
CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<TStartup>(); }).Build().Run();
}
public static Assembly Assembly { get; private set; }
public static string CurrentDirectory { get; private set; }
}
And finally, here is Program, located in WebAPI:
public static class Program
{
public static void Main(string[] args)
{
GetExecutingAssembly().Run<Startup>(args);
}
}
As was pointed out in one of the comments, I was missing a link to the API assembly.
from:
services.AddMvc(options => { options.EnableEndpointRouting = false; });
to:
services.AddMvc(options => { options.EnableEndpointRouting = false; }).AddApplicationPart(Api.Assembly);
and here is some extra information on AddApplicationPart.
SmtpConfig contains my credentials which I want to use in a test class.
appsettings.development.json
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"SmtpConfig": {
"credentials": "username:password"
}
}
Here I configure the smtpConfig to be injected in classes (in controller classes works very fine!)
Startup.cs
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.Configure<SmtpConfig(
Configuration.GetSection(nameof(SmtpConfig)
));
}
I want to access credentials from appsettings.development.json in tests, because on another server I will have another config file.
//important usings
using Microsoft.Extensions.Options;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class SomeControllerAPITest
{
private SmtpConfig _smtpConfig;
public SomeControllerAPITest(IOptions<SmtpConfig> smtpConfig)
{
_smtpConfig = smtpConfig.Value;
}
[TestMethod]
public void Post_ReturnsCreatedInstance()
{
var credentials = _smtpConfig.credentials;
//use that credentials
...
//call remote server
...
}
}
Is it possible to do that?
- Create class file into testProject
public static IConfiguration getConfig(){
var config = new ConfigurationBuilder()
.SetBasePath("/Users/Project/")
.AddJsonFile("appsettings.json")
.Build();
return config;
}
[TestClass]
public class TestMasterClass
{
public static IConfiguration _configuration { get; set; }
public TestMasterClass()
{
_configuration = AnotherClassFile.getConfig();
}
[TestMethod]
public void TestConfigElasticSearch()
{
var elasticSearch = _configuration["ElasticSearchConfig:Link01"];
Assert.IsNotNull(elasticSearch);
}
}
You can use the same Microsoft.Extensions.Configuration binding functionality to build an identically populated IOptions<TConfiguration> instance. Here is a rough equivalent of how we implemented this for our test code:
public class TestSmtpConfigOptions : IOptions<SmtpConfig> {
private static Lazy<SmtpConfig> configuration { get; }
static TestSmtpConfigOptions() {
configuration = new Lazy<SmtpConfig>(GetConfiguration);
}
public SmtpConfig Value {
get { return configuration.Value; }
}
private static SmtpConfig GetConfiguration() {
var configuration = new SmtpConfig();
var path = Path.Combine("config", "appsettings.development.json");
new ConfigurationBuilder()
.SetBasePath("path/to/base/directory/of/project")
.AddJsonFile(path, optional: true)
.Build()
.GetSection(nameof(SmtpConfig))
.Bind(configuration);
return configuration;
}
}
Then, in your fixture, you only need to instantiate it:
[TestClass]
public class SomeControllerAPITest {
private SmtpConfig _smtpConfig;
public SomeControllerAPITest() {
_smtpConfig = new TestSmtpConfigOptions().Value;
}
[TestMethod]
public void Post_ReturnsCreatedInstance() {
var credentials = _smtpConfig.credentials;
//use that credentials
...
//call remote server
...
}
}
In case you care about cross platform paths and don't mind a little extra complexity, here's a little class we use to get the base path in a cross-platform way for our xUnit test runner. This means we use TestConfiguration.BasePath instead of "path/to/base/directory/of/project" in the example above.
internal static class TestConfiguration {
internal static string BasePath { get; }
static TestConfiguration() {
BasePath = Environment.GetEnvironmentVariable("BASE_DIRECTORY");
if (BasePath == null) {
BasePath = AppContext.BaseDirectory;
// cross-platform equivalent of "../../../../../"
for (var index = 0; index < 5; index++) {
BasePath = Directory.GetParent(BasePath).FullName;
}
}
}
internal static string ResolvePath(string relativePath) {
return Path.Combine(BasePath, relativePath);
}
}