Azure Webjob 3.0 - Unable to read Configuration variable from appsettings.json in Timer Function - azure-webjobs

I'm struggling to read any variables from my appsettings.json file in an Azure Webjob SDK 3.0 project.
I am using:
Microsoft.Azure.WebJobs.Core version="3.0.6"
Microsoft.Azure.WebJobs.Extensions version="3.0.2"
Microsoft.Extensions.Configuration version="2.2.0"
I've tried the following:
System.Configuration.ConfigurationManager.AppSettings["ApiUrl"]
but this returns null and fails to read. I've checked the documentation and other questions on SO, but currently it's like finding a needle in a haystack.
Program.cs
static async Task Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices()
.AddAzureStorage()
.AddTimers();
b.UseHostId(Environment.UserName.ToLowerInvariant());
})
.ConfigureAppConfiguration((b, c)=>
{
var env = b.HostingEnvironment;
c.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
c.AddCommandLine(args);
c.AddEnvironmentVariables();
c.Build();
})
.ConfigureLogging((context, b) =>
{
b.SetMinimumLevel(LogLevel.Debug);
b.AddConsole();
string appInsightsKey = context.Configuration["APPINSIGHTS_INSTRUMENTATIONKEY"];
if (!string.IsNullOrEmpty(appInsightsKey))
{
b.AddApplicationInsights(o => o.InstrumentationKey = appInsightsKey);
}
})
.UseConsoleLifetime();
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
Functions.cs
public static async Task ProcessAsync([TimerTrigger("0 */3 * * * *")] TimerInfo timerInfo, ILogger log)
{
//I want to read my appsettings.json here!
}
appsettings.json example:
{
"AzureWebJobsDashboard": "################",
"AzureWebJobsStorage": "################",
"ApiUrl": "#############",
"APPINSIGHTS_INSTRUMENTATIONKEY": "############"
}

IGNORE THIS - STILL not working
Just figured it out! Initialising this at the start of my Functions class fixed it for me:
private readonly IConfiguration _configuration;
public Functions(IConfiguration configuration)
{
_configuration = configuration;
}
public static async Task ProcessAsync([TimerTrigger("0 */3 * * * *")] TimerInfo timerInfo, ILogger log)
{
var conf = _configuration["ApiUrl"];
}
This works, but causes the WebJob to run as one function. Each run should be broken out as each individual run, under the WebJob function parent in the logs. Incredibly frustrating.
If anybody can help, please let me know.

Related

Should a triggered Webjob complete?

I have created a very simple WebJob with a TimerTrigger, for example;
static async Task Main()
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddTimers();
});
builder.ConfigureLogging((context, b) =>
{
b.AddConsole();
});
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
}
public class Functions
{
public static void ProcessTimerMessage([TimerTrigger("*/30 * * * * *", RunOnStartup = true)] TimerInfo timerInfo, ILogger logger)
{
logger.LogInformation("I am here");
}
}
When I run this in Azure it never finishes. I was expecting this to complete on each run and then start again on the next trigger. Instead it never stops running:
The TimerTrigger for WebJobs runs as a singleton instance at startup and will run continuously, internally keeping track of the interval it runs at (based on the provide cron pattern). If any single invocation takes longer than the interval, then it will contiune processing the work it's doing and skip the overlapping invocation until the current work is complete.
You can read the details here:
https://github.com/Azure/azure-webjobs-sdk-extensions/wiki/TimerTrigger

AddSingleton won't accept AddSerilog

I'm facing an issue where I'm trying to write a more smart console app with logging and configuration available.
This is what I have so far:
namespace Client
{
public class Program
{
public static IConfigurationRoot Configuration;
private static int Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console(Serilog.Events.LogEventLevel.Debug)
.MinimumLevel.Debug()
.Enrich.FromLogContext()
.CreateLogger();
try
{
MainAsync(args).ConfigureAwait(false);
return 0;
}
catch
{
return 1;
}
}
private static async Task MainAsync(string[] args)
{
// Create service collection
Log.Information("Creating service collection");
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
// Create service provider
Log.Information("Building service provider");
IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
try
{
Log.Information("Starting service");
await serviceProvider.GetService<App>().Run();
Log.Information("Ending service");
}
catch (Exception ex)
{
Log.Fatal(ex, "Error running service");
throw;
}
finally
{
Log.CloseAndFlush();
}
}
private static void ConfigureServices(IServiceCollection services)
{
// Add logging
services.AddSingleton(
LoggerFactory.Create(
builder => { builder.AddSerilog(dispose: true); }));
services.AddLogging();
// Build configuration
Configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetParent(AppContext.BaseDirectory).FullName)
.AddJsonFile("appSettings.json", false)
.Build();
// Add access to generic IConfigurationRoot
services.AddSingleton(Configuration);
// Add app
services.AddTransient<App>();
}
}
}
I'm facing an issue in ConfigureServices method on line builder.AddSerilog for the life of me, I cannot figure out why it is not able to resolve AddSerilog
I was missing a package: Serilog.Extensions.Logging
For some reason, VS or even Resharper was not able to suggest this.

Integrated Unit Testing is not running in ASP.NET core MVC/Web API

I am developing a web API using ASP.Net core. I am doing integrated testing to my project. I am following this link, https://koukia.ca/integration-testing-in-asp-net-core-2-0-51d14ede3968. This is my code.
I have the controller to be tested in the thegoodyard.api project.
namespace thegoodyard.api.Controllers
{
[Produces("application/json")]
[Route("api/category")]
public class CategoryController: Controller
{
[HttpGet("details/{id}")]
public string GetCategory(int id = 0)
{
return "This is the message: " + id.ToString();
}
}
}
I added a new unit test project called thegoodyard.tests to the solution. I added a TestServerFixture class with the following definition
namespace thegoodyard.tests
{
public class TestServerFixture : IDisposable
{
private readonly TestServer _testServer;
public HttpClient Client { get; }
public TestServerFixture()
{
var builder = new WebHostBuilder()
.UseContentRoot(GetContentRootPath())
.UseEnvironment("Development")
.UseStartup<Startup>(); // Uses Start up class from your API Host project to configure the test server
_testServer = new TestServer(builder);
Client = _testServer.CreateClient();
}
private string GetContentRootPath()
{
var testProjectPath = PlatformServices.Default.Application.ApplicationBasePath;
var relativePathToHostProject = #"..\..\..\..\..\..\thegoodyard.api";
return Path.Combine(testProjectPath, relativePathToHostProject);
}
public void Dispose()
{
Client.Dispose();
_testServer.Dispose();
}
}
}
Then again in the test project, I created a new class called, CategoryControllerTests with the following definition.
namespace thegoodyard.tests
{
public class CategoryControllerTests: IClassFixture<TestServerFixture>
{
private readonly TestServerFixture _fixture;
public CategoryControllerTests(TestServerFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task GetCategoryDetai()
{
var response = await _fixture.Client.GetAsync("api/category/details/3");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
bool containMessage = false; //responseString.Contains("This is the message: 3"); - I commented on purpose to make the test fails.
Assert.True(containMessage);
}
}
}
Then I right on the test method and clicked run tests in the option to run the test. But none of the tests was run. This is the output.
What is missing in my code? How can I get my integrated test running?
Please check following NuGet packages in your project:
Microsoft.AspNetCore.TestHost
Microsoft.NET.Test.Sdk
xunit
xunit.runner.visualstudio
Perhaps there's a build error that's preventing the project from being compiled. There's really not enough information here to say for sure. Rebuild your solution, and ensure there's no errors.
Aside from that, you can remove some variables by reducing the test code needed. ASP.NET Core includes a WebApplicationFactory<TEntryPoint> fixture out of the box for bootstrapping a test server. You can therefore change your test code to just:
public class CategoryControllerTests: IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public CategoryControllerTests(WebApplicationFactory<Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task GetCategoryDetail()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("api/category/details/3");
...
See the documentation for additional information and more advanced scenarios.
This way works fine for xUnit based intergration tests, which use Startup configuration. the code blow also demonstrates how to override some settings in appSetting.json to specific values for testing, as well as how to access to DI services.
using System;
using System.Net.Http;
using MyNamespace.Web;
using MyNamespace.Services;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace MyNamespace.Tests
{
public class TestServerDependent : IDisposable
{
private readonly TestServerFixture _fixture;
public TestServer TestServer => _fixture.Server;
public HttpClient Client => _fixture.Client;
public TestServerDependent()
{
_fixture = new TestServerFixture();
var myService = GetService<IMyService>();
// myService.PerformAnyPreparationsForTests();
}
protected TService GetService<TService>()
where TService : class
{
return _fixture.GetService<TService>();
}
public void Dispose()
{
_fixture?.Dispose();
}
}
public class TestServerFixture : IDisposable
{
public TestServer Server { get; }
public HttpClient Client { get; }
public TestServerFixture()
{
var hostBuilder = WebHost.CreateDefaultBuilder()
.ConfigureAppConfiguration(
(builderContext, config) =>
{
var env = builderContext.HostingEnvironment;
config
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile("appsettings.Testing.json", optional: false,
reloadOnChange: true);
})
.ConfigureLogging(
(hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseStartup<Startup>();
Server = new TestServer(hostBuilder);
Client = Server.CreateClient();
}
public void Dispose()
{
Server.Dispose();
Client.Dispose();
}
public TService GetService<TService>()
where TService : class
{
return Server?.Host?.Services?.GetService(typeof(TService)) as TService;
}
}
}
How the simple integration test might look like with the described above:
using System.Net;
using Xunit;
namespace MyNamespace.Tests
{
public class SimpleIntegrationTest : TestServerDependent
{
[Fact]
public void RedirectToLoginPage()
{
var httpResponseMessage = Client.GetAsync("/").Result;
// Smoke test to make sure you are redirected (to Login page for instance)
Assert.Equal(HttpStatusCode.Redirect, httpResponseMessage.StatusCode);
}
}
}

following constructor parameters did not have matching fixture data : DbContextOptions'1options [duplicate]

I have build a WebAPI and apart from my tests running on Postman I would like to implement some Integration/Unit tests.
Now my business logic is very thin, most of the time its more of CRUD actions, therefore I wanted to start with testing my Controllers.
I have a basic setup. Repository pattern (interfaces), Services (business logic) and Controllers.
The flow goes Controller (DI Service) -> Service (DI Repo) -> Repo Action!
So what I did was override my Startup file to change into a in memory database and the rest should be fine (I would assume) Services are added, repos are added and now I am pointing into a in memory DB which is fine for my basic testing.
namespace API.UnitTests
{
public class TestStartup : Startup
{
public TestStartup(IHostingEnvironment env)
: base(env)
{
}
public void ConfigureTestServices(IServiceCollection services)
{
base.ConfigureServices(services);
//services.Replace<IService, IMockedService>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
base.Configure(app, env, loggerFactory);
}
public override void SetUpDataBase(IServiceCollection services)
{
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
var connectionString = connectionStringBuilder.ToString();
var connection = new SqliteConnection(connectionString);
services
.AddEntityFrameworkSqlite()
.AddDbContext<ApplicationDbContext>(
options => options.UseSqlite(connection)
);
}
}
}
I wrote my first test, but the DatasourceService is not there:
The following constructor parameters did not have matching fixture data: DatasourceService datasourceService
namespace API.UnitTests
{
public class DatasourceControllerTest
{
private readonly DatasourceService _datasourceService;
public DatasourceControllerTest(DatasourceService datasourceService)
{
_datasourceService = datasourceService;
}
[Xunit.Theory,
InlineData(1)]
public void GetAll(int companyFk) {
Assert.NotEmpty(_datasourceService.GetAll(companyFk));
}
}
}
What am I missing?
You can't use dependency injection on test classes. You can only let xunit inject special fixtures via constructor (see docs).
For Integration Testing you want to use the TestServer class from Microsoft.AspNetCore.TestHost package and a separate Startup.cs class (easier to setup configuration than inheritance imho).
public class TestStartup : Startup
{
public TestStartup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureTestServices(IServiceCollection services)
{
services.Replace(ServiceDescriptor.Scoped<IService, MockedService>());
services.AddEntityFrameworkSqlite()
.AddDbContext<ApplicationDbContext>(
options => options.UseSqlite(connection)
);
}
public void Configure(IApplicationBuilder app)
{
// your usual registrations there
}
}
In your unit test project, you need to create an instance of the TestServer and perform the test.
public class DatasourceControllerTest
{
private readonly TestServer _server;
private readonly HttpClient _client;
public DatasourceControllerTest()
{
// Arrange
_server = new TestServer(new WebHostBuilder()
.UseStartup<TestStartup>());
_client = _server.CreateClient();
}
[Xunit.Theory,
InlineData(1)]
public async Task GetAll(int companyFk) {
// Act
var response = await _client.GetAsync($"/api/datasource/{companyFk}");
// expected result from rest service
var expected = #"[{""data"":""value1"", ""data2"":""value2""}]";
// Assert
// This makes sure, you return a success http code back in case of 4xx status codes
// or exceptions (5xx codes) it throws an exception
response.EnsureSuccessStatusCode();
var resultString = await response.Content.ReadAsStringAsync();
Assert.Equals(resultString, expectedString);
}
}
Now, when you call operations which write to the database, you can also check if the data is really written to the database:
[Xunit.Theory,
InlineData(1)]
public async Task GetAll(int companyFk) {
// Act
var response = await _client.DeleteAsync($"/api/datasource/{companyFk}");
// expected result from rest service
// Assert
response.EnsureSuccessStatusCode();
// now check if its really gone in the database. For this you need an instance
// of the in memory Sqlite DB. TestServer has a property Host, which is an IWebHost
// and it has a property Services which is the IoC container
var provider = _server.Host.Services;
var dbContext = provider.GetRequiredService<ApplicationDbContext>();
var result = await dbContext.YourTable.Where(entity => entity.Id == companyFk).Any();
// if it was deleted, the query should result in false
Assert.False(result);
}
Now you can use Xunit.DependencyInjection in your tests.
namespace Your.Test.Project
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IDependency, DependencyClass>();
}
}
}
your DI-classes:
public interface IDependency
{
int Value { get; }
}
internal class DependencyClass : IDependency
{
public int Value => 1;
}
and XUnit-test:
public class MyAwesomeTests
{
private readonly IDependency _d;
public MyAwesomeTests(IDependency d) => _d = d;
[Fact]
public void AssertThatWeDoStuff()
{
Assert.Equal(1, _d.Value);
}
}

Odd NHibernate Sqllite.InMemory behaviour

I am trying to setup database unit tests using the Sqllite.InMemory functionality.
If I run an unit test, everything is working fine. If I run the same test a second time I get an System.Data.SQLite.SQLiteException: no such table: Person
After waiting for a while and/or (?) restarting Visual studio I can run the unit test once again.
Is there something wrong with the configuration or the session handling?
public abstract class InMemoryDatabaseFixture : IDisposable
{
private const string ConnectionString
= "Data Source=:memory:;Version=3;New=True;Pooling=True;Max Pool Size=1;";
private readonly ISessionFactory _sessionFactory;
private readonly ISession _session;
protected InMemoryDatabaseFixture()
{
var config = SQLiteConfiguration.Standard.InMemory().ShowSql().ConnectionString(ConnectionString);
_sessionFactory = Fluently.Configure()
.Database(config)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<SessionContainer>())
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(true, true))
.BuildSessionFactory();
_session = _sessionFactory.OpenSession();
SessionContainer = MockRepository.GenerateMock<ISessionContainer>();
SessionContainer.Stub(sc => sc.SessionFactory).Return(_sessionFactory);
SessionContainer.Stub(sc => sc.Session).Return(_session);
}
protected ISessionContainer SessionContainer { get; private set; }
public void Dispose()
{
_sessionFactory.Dispose();
_session.Dispose();
}
}
Here a simple usage of the base class:
[TestFixture]
public class FinderFixture : InMemoryDatabaseFixture
{
[Test]
public void Test()
{
var finder = new Finder(SessionContainer);
var result = finder.Find();
Assert.That(result, Is.Not.Null);
}
}
Update: After some trials here is finally my working configuration. Exporting the schema after building the SessionFactory is doing the magic.
Configuration configuration = null;
_sessionFactory = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory().ShowSql())
.ExposeConfiguration(cfg => configuration = cfg)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<SessionContainer>())
.BuildSessionFactory();
_session = _sessionFactory.OpenSession();
var export = new SchemaExport(configuration);
export.Execute(true, true, false, _session.Connection, null);
You have requested that connection pooling in the ADO.NET provider be enabled. This will keep the underlying connection active even after NHibernate has closed it.
In my own unit tests, I have simply (based on your original code):
_sessionFactory = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<SessionContainer>())
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(true, true))
.BuildSessionFactory();
Also, since the session is generated from the session factory, it would be prudent to dispose all sessions before disposing the session factory.