ASP.Net Core 2.0: Creating UrlHelper without request - c#

I'm working on creating a UrlHelper for a background worker to create callback urls, which means it's not part of a normal request where I could just ask for it through DI.
In ASP.Net 5 I could just create a HttpRequest and give it the same HttpConfiguration I used to build my app, but in ASP.Net Core 2.0 the UrlHelper depends on a full ActionContext which is a bit harder to craft.
I have a working prototype, but it's using a nasty hack to smuggle the route data out of the application startup process. Is there a better way to do this?
public class Capture
{
public IRouter Router { get; set; }
}
public static class Ext
{
// Step 1: Inject smuggler when building web host
public static IWebHostBuilder SniffRouteData(this IWebHostBuilder builder)
{
return builder.ConfigureServices(svc => svc.AddSingleton<Capture>());
}
// Step 2: Swipe the route data in application startup
public static IApplicationBuilder UseMvcAndSniffRoutes(this IApplicationBuilder app)
{
var capture = app.ApplicationServices.GetRequiredService<Capture>();
IRouteBuilder capturedRoutes = null;
app.UseMvc(routeBuilder => capturedRoutes = routeBuilder);
capture.Router = capturedRoutes?.Build();
return app;
}
// Step 3: Build the UrlHelper using the captured routes and webhost
public static IUrlHelper GetStaticUrlHelper(this IWebHost host, string baseUri)
=> GetStaticUrlHelper(host, new Uri(baseUri));
public static IUrlHelper GetStaticUrlHelper(this IWebHost host, Uri baseUri)
{
HttpContext httpContext = new DefaultHttpContext()
{
RequestServices = host.Services,
Request =
{
Scheme = baseUri.Scheme,
Host = HostString.FromUriComponent(baseUri),
PathBase = PathString.FromUriComponent(baseUri),
},
};
var captured = host.Services.GetRequiredService<Capture>();
var actionContext = new ActionContext
{
HttpContext = httpContext,
RouteData = new RouteData { Routers = { captured.Router }},
ActionDescriptor = new ActionDescriptor(),
};
return new UrlHelper(actionContext);
}
}
// Based on dotnet new webapi
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args);//.Run();
}
public static IWebHost BuildWebHost(string[] args)
{
var captured = new Capture();
var webhost = WebHost.CreateDefaultBuilder(args)
.SniffRouteData()
.UseStartup<Startup>()
.Build();
var urlHelper = webhost.GetStaticUrlHelper("https://my.internal.service:48923/somepath");
Console.WriteLine("YO! " + urlHelper.Link(nameof(ValuesController), null));
return webhost;
}
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, Capture capture)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvcAndSniffRoutes();
}
}
[Route("api/[controller]", Name = nameof(ValuesController))]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// etc
}

Browsing the sources it seems there is no less hacky solution.
In the UseMvc() method the IRouter object being built is passed to the RouterMiddleware, which stores it in a private field and exposes it only to the requests. So reflection would be your only other option, which is obviously out of the running.
However, if you need to generate only static paths using IUrlHelper.Content() you won't need the router as the default implementation won't use it. In this case you can create the helper like this:
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var urlHelper = new UrlHelper(actionContext);

With ASP.NET Core 2.2 releasing today, they've added a LinkGenerator class that sounds like it will solve this problem (the tests look promising). I'm eager to try it, but as I'm not actively working on the project where I needed this at the moment, it will have to wait a bit. But I'm optimistic enough to mark this as a new answer.

Related

Implementing Azure Event Grid event handler in a Web API which subscribes to changes in Azure App Configuration

I am exploring various methods to get configuration data from Azure App Configuration and got most methods working however I am struggling to implement the Azure Event Grid event handler in a Web API on ASP.NET Core 3.1. I want to know if it's possible to notify the Web API about changes through the Event Grid instead of it polling when its cache set up for Azure App configuration has expired. Once notified, the configuration values should update and the new values should feed to any controllers when someone makes a request to the Web API.
I have included the contents in my Program.cs, Startup.cs, service bus consumer where I set up the event grid subscriber and a sample controller.
From what I've read from Microsoft's documentation about this, my understanding is that the IConfigurationRefresher.ProcessPushNotification method resets the cache expiration to a short random delay rather the cache expiration set in the CreateHostedBuilder method and when the IConfigurationRefresher.TryRefreshAsync() is called it updates the configuration values.
The issue I am having is not being able to inject an instance of the concrete class for IConfigurationRefresher to the RegisterRefreshEventHandler method and in turn call ProcessPushNotification to reset the expiration time.
I may also be going about this wrongly as I'm assuming the RegisterRefreshEventHandler code which works in a console application will work for a Web API as well. Please let me know if my approach will work?
Program.cs
public class Program
{
private static IConfiguration _configuration;
private static IConfigurationRefresher _refresher;
public static void Main(string[] args)
{
_configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", optional: false).Build();
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
var settings = config.Build();
config.AddAzureAppConfiguration(options =>
{
options
.Connect(_configuration["AppConfig"]).ConfigureRefresh(
refresh => refresh.Register(key: "Api:Sentinel", refreshAll: true).SetCacheExpiration(TimeSpan.FromDays(1))
).Select(KeyFilter.Any, "ClientA");
_refresher = options.GetRefresher();
}
);
}).UseStartup<Startup>());
}
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration, IConfigurationRefresher configurationRefresher)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<TFASettings>(Configuration.GetSection("Api:TFA"));
services.AddControllers();
services.AddAzureAppConfiguration();
services.AddSingleton<IServiceBusConsumer, ServiceBusConsumer>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
var bus = app.ApplicationServices.GetRequiredService<IServiceBusConsumer>();
bus.RegisterRefreshEventHandler();
app.UseAzureAppConfiguration();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
ServiceBusConsumer.cs
public class ServiceBusConsumer : IServiceBusConsumer
{
public IConfigurationRefresher configurationRefresher;
public ServiceBusConsumer()
{
}
public void RegisterRefreshEventHandler()
{
SubscriptionClient serviceBusClient = new SubscriptionClient("*****", "*****", "*****");
serviceBusClient.RegisterMessageHandler(
handler: (message, cancellationToken) =>
{
// Build EventGridEvent from notification message
EventGridEvent eventGridEvent = EventGridEvent.Parse(BinaryData.FromBytes(message.Body));
// Create PushNotification from eventGridEvent
eventGridEvent.TryCreatePushNotification(out PushNotification pushNotification);
//// Prompt Configuration Refresh based on the PushNotification
//configurationRefresher.ProcessPushNotification(pushNotification);
return Task.CompletedTask;
},
exceptionReceivedHandler: (exceptionargs) =>
{
Console.WriteLine($"{exceptionargs.Exception}");
return Task.CompletedTask;
});
}
}
Sample controller
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly TFASettings _tfaSettings;
public WeatherForecastController(IOptionsSnapshot<TFASettings> tfaSettings)
{
_tfaSettings = tfaSettings.Value;
}
[HttpGet]
public WeatherForecast Get()
{
return new WeatherForecast
{
AuthenticationText = _tfaSettings.AuthenticationWording
};
}
}
You can get the concrete instance of IConfigurationRefresher through dependency injection. You can even call RegisterRefreshEventHandler() from the constructor too. Your ServiceBusConsumer.cs can look something like this.
private IConfigurationRefresher _configurationRefresher;
public ServiceBusConsumer(IConfigurationRefresherProvider refresherProvider)
{
_configurationRefresher = refresherProvider.Refreshers.FirstOrDefault();
RegisterRefreshEventHandler();
}

How to setup the DI container in a .NET Core console app?

I created a new .NET Core console app and installed the following packages
Microsoft.Extensions.Configuration
Microsoft.Extensions.Configuration.EnvironmentVariables
Microsoft.Extensions.Configuration.Json
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Options
Microsoft.Extensions.Options.ConfigurationExtensions
I created a appsettings.json file for the configuration
{
"app": {
"foo": "bar"
}
}
and I want to map those values to a class
internal class AppOptions
{
public string Foo { get; set; }
}
I also want to validate the options during configuration so I added a validating class
internal class AppOptionsValidator : IValidateOptions<AppOptions>
{
public ValidateOptionsResult Validate(string name, AppOptions options)
{
IList<string> validationFailures = new List<string>();
if (string.IsNullOrEmpty(options.Foo))
validationFailures.Add("Foo is required.");
return validationFailures.Any()
? ValidateOptionsResult.Fail(validationFailures)
: ValidateOptionsResult.Success;
}
}
I want to setup the DI container and created a testing scenario
static void Main(string[] args)
{
ConfigureServices();
Console.ReadLine();
}
private static void ConfigureServices()
{
IServiceCollection serviceCollection = new ServiceCollection();
IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
// Setup configuration service
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.AddEnvironmentVariables()
.Build();
serviceCollection.AddSingleton(configuration);
// Setup options
IConfiguration configurationFromDI = serviceProvider.GetService<IConfiguration>(); // This is just for testing purposes
IConfigurationSection myConfigurationSection = configurationFromDI.GetSection("app");
serviceCollection.AddSingleton<IValidateOptions<AppOptions>, AppOptionsValidator>();
serviceCollection.Configure<AppOptions>(myConfigurationSection);
// Try to read the current options
IOptions<AppOptions> appOptions = serviceProvider.GetService<IOptions<AppOptions>>();
Console.WriteLine(appOptions.Value.Foo);
}
Unfortunately the variable configurationFromDI is null. So the variable configuration wasn't added to the DI container.
How do I setup the Dependency Injection for console applications correctly?
The call to BuildServiceProvider should be made after all services are registered.
There's no need to write all of this code though. Since you use so many extensions already, it's better (and easier) to use the generic Host, the same way an ASP.NET Core application does and use its ConfigureServices, ConfigureAppConfiguration methods:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureHostConfiguration(configuration =>
{
configuration....;
});
.ConfigureServices((hostContext, services) =>
{
var myConfigurationSection = configuration.GetSection("app");
services.AddSingleton<IValidateOptions<AppOptions>, AppOptionsValidator>();
services.Configure<AppOptions>(myConfigurationSection);
});
}
Configuration is available through the HostBuilderContext.Configuration property.
CreateDefaultBuilder sets the current folder, configures environment variables and the use of appsettings.json files so there's no need to add them explicitly.
Appsettings.json copy settings
In a web app template, appsettings.json files are added automatically with the Build Action property set to Content and the Copy to Output action to Copy if Newer.
There are no such files in a Console app. When a new appsettings.json file is added by hand, its Build Action is None and Copy to Never. When the application is debugged the current directory is bin\Debug. With the default settings, appsettings.json won't be copied to bin/Debug
Build Action will have to change to Content and Copy should be set to Copy if Newer or Copy Always.
DI in Console project
You can setup DI in any executable .net-core app and configure services in a Startup class (just like web projects) by extending IHostBuilder:
public static class HostBuilderExtensions
{
private const string ConfigureServicesMethodName = "ConfigureServices";
public static IHostBuilder UseStartup<TStartup>(
this IHostBuilder hostBuilder) where TStartup : class
{
hostBuilder.ConfigureServices((ctx, serviceCollection) =>
{
var cfgServicesMethod = typeof(TStartup).GetMethod(
ConfigureServicesMethodName, new Type[] { typeof(IServiceCollection) });
var hasConfigCtor = typeof(TStartup).GetConstructor(
new Type[] { typeof(IConfiguration) }) != null;
var startUpObj = hasConfigCtor ?
(TStartup)Activator.CreateInstance(typeof(TStartup), ctx.Configuration) :
(TStartup)Activator.CreateInstance(typeof(TStartup), null);
cfgServicesMethod?.Invoke(startUpObj, new object[] { serviceCollection });
});
return hostBuilder;
}
}
Now, you have an extension method UseStartup<>() that can be called in Program.cs (the last line):
public class Program
{
public static void Main(string[] args)
{
// for console app
CreateHostBuilder(args).Build().Run();
// For winforms app (commented)
// Application.SetHighDpiMode(HighDpiMode.SystemAware);
// Application.EnableVisualStyles();
// Application.SetCompatibleTextRenderingDefault(false);
// var host = CreateHostBuilder(args).Build();
// Application.Run(host.Services.GetRequiredService<MainForm>());
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
config.AddJsonFile("appsettings.json", optional: false);
config.AddEnvironmentVariables();
// any other configurations
})
.UseStartup<MyStartup>();
}
Finally, Add your own Startup class (here, MyStartup.cs), and inject IConfiguration from constructor:
public class MyStartup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
// services.AddBlahBlahBlah()
}
}
P.S: you get null because you call .BuildServiceProvider before registering IConfiguration
Mapping appsettings.json
For mapping appsettings.json values to a type, define an empty interface like IConfigSection (just for generic constraints reason):
public interface IConfigSection
{
}
Then, extend IConfiguration interface like follow:
public static class ConfigurationExtensions
{
public static TConfig GetConfigSection<TConfig>(this IConfiguration configuration) where TConfig : IConfigSection, new()
{
var instance = new TConfig();
var typeName = typeof(TConfig).Name;
configuration.GetSection(typeName).Bind(instance);
return instance;
}
}
Extension methdod GetConfigSection<>() do the mapping for you. Just define your config classes that implement IConfigSection:
public class AppConfigSection : IConfigSection
{
public bool IsLocal { get; set; }
public bool UseSqliteForLocal { get; set; }
public bool UseSqliteForServer { get; set; }
}
Below is how your appsettings.json should look like (class name and property names should match):
{
.......
"AppConfigSection": {
"IsLocal": false,
"UseSqliteForServer": false,
"UseSqliteForLocal": false
},
.....
}
And finally, retrieve your settings and map them to your ConfigSections as follow:
// configuration is the injected IConfiguration
AppConfigSection appConfig = configuration.GetConfigSection<AppConfigSection>();

Google API Client library authentication with .NET Core

We are using .NET Core 3.1 to develop a web application. We want to use Google.Apis.Drive.v3 NuGet package to list all files saved in Google Drive. The account from which we want to retrieve files will always be the same, ex. company-data#company.com. We found official documentation on how to authenticate in web applications. However, this example doesn't seem to be working in .NET Core.
Can anyone provide a simple example on how to authenticate against Google Drive API and list all files. Official documentation doesn't cover .NET Core at all.
EDIT: There is a very similar question here. Unfortunately, there are no answers.
This is an example with Google analytics let me know if you need help altering it for drive.
startup.cs
public class Client
{
public class Web
{
public string client_id { get; set; }
public string client_secret { get; set; }
}
public Web web { get; set; }
}
public class ClientInfo
{
public Client Client { get; set; }
private readonly IConfiguration _configuration;
public ClientInfo(IConfiguration configuration)
{
_configuration = configuration;
Client = Load();
}
private Client Load()
{
var filePath = _configuration["TEST_WEB_CLIENT_SECRET_FILENAME"];
if (string.IsNullOrEmpty(filePath))
{
throw new InvalidOperationException(
$"Please set the TEST_WEB_CLIENT_SECRET_FILENAME environment variable before running tests.");
}
if (!File.Exists(filePath))
{
throw new InvalidOperationException(
$"Please set the TEST_WEB_CLIENT_SECRET_FILENAME environment variable before running tests.");
}
var x = File.ReadAllText(filePath);
return JsonConvert.DeserializeObject<Client>(File.ReadAllText(filePath));
}
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ClientInfo>();
services.AddControllers();
services.AddAuthentication(o =>
{
// This is for challenges to go directly to the Google OpenID Handler, so there's no
// need to add an AccountController that emits challenges for Login.
o.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
// This is for forbids to go directly to the Google OpenID Handler, which checks if
// extra scopes are required and does automatic incremental auth.
o.DefaultForbidScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogleOpenIdConnect(options =>
{
var clientInfo = new ClientInfo(Configuration);
options.ClientId = clientInfo.Client.web.client_id;
options.ClientSecret = clientInfo.Client.web.client_secret;
});
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
}
Controller with Auth
[ApiController]
[Route("[controller]")]
public class GAAnalyticsController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
public GAAnalyticsController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
// Test showing use of incremental auth.
// This attribute states that the listed scope(s) must be authorized in the handler.
[GoogleScopedAuthorize(AnalyticsReportingService.ScopeConstants.AnalyticsReadonly)]
public async Task<GetReportsResponse> Get([FromServices] IGoogleAuthProvider auth, [FromServices] ClientInfo clientInfo)
{
var GoogleAnalyticsViewId = "78110423";
var cred = await auth.GetCredentialAsync();
var service = new AnalyticsReportingService(new BaseClientService.Initializer
{
HttpClientInitializer = cred
});
var dateRange = new DateRange
{
StartDate = "2015-06-15",
EndDate = "2015-06-30"
};
// Create the Metrics object.
var sessions = new Metric
{
Expression = "ga:sessions",
Alias = "Sessions"
};
//Create the Dimensions object.
var browser = new Dimension
{
Name = "ga:browser"
};
// Create the ReportRequest object.
var reportRequest = new ReportRequest
{
ViewId = GoogleAnalyticsViewId,
DateRanges = new List<DateRange> {dateRange},
Dimensions = new List<Dimension> {browser},
Metrics = new List<Metric> {sessions}
};
var requests = new List<ReportRequest> {reportRequest};
// Create the GetReportsRequest object.
var getReport = new GetReportsRequest {ReportRequests = requests};
// Make the request.
var response = service.Reports.BatchGet(getReport).Execute();
return response;
}
}
You can use Google.Apis.Auth.AspNetCore3 for authenticating in .NET Core 3.1. The Google.Apis.Auth.AspNetCore3.IntegrationTests is a good example (it's just an ASP.NET Core 3 Web Application) of how to use the library and shows all of its features. Feel free to create issues in https://github.com/googleapis/google-api-dotnet-client if you encounter any problems.

Connecting to remote Jaegertracing with C# .Net Core

Im having some issues here with Opentracing and Jaegertracing when it comes to C#. I have had this working before, but with Java projects. So I start to wonder what Im missing when it comes to C# .NET Core web service.
This is my class to start my tracer to be used
public static class MyTracer
{
public static ITracer tracer = null;
public static ITracer InitTracer()
{
Environment.SetEnvironmentVariable("JAEGER_SERVICE_NAME", "my-store");
Environment.SetEnvironmentVariable("JAEGER_AGENT_HOST", "192.168.2.27");
Environment.SetEnvironmentVariable("JAEGER_AGENT_PORT", "6831");
Environment.SetEnvironmentVariable("JAEGER_SAMPLER_TYPE", "const");
Environment.SetEnvironmentVariable("JAEGER_REPORTER_LOG_SPANS", "false");
Environment.SetEnvironmentVariable("JAEGER_SAMPLER_PARAM","1");
Environment.SetEnvironmentVariable("JAEGER_SAMPLER_MANAGER_HOST_PORT", "5778");
Environment.SetEnvironmentVariable("JAEGER_REPORTER_FLUSH_INTERVAL" , "1000");
Environment.SetEnvironmentVariable("JAEGER_REPORTER_MAX_QUEUE_SIZE" , "100");
var loggerFactory = new LoggerFactory();
var config = Configuration.FromEnv(loggerFactory);
tracer = config.GetTracer();
if (!GlobalTracer.IsRegistered())
{
GlobalTracer.Register(tracer);
}
return tracer;
}
}
Controller code that should report to the Jaeger agent and collector for show in the UI.
[Route("api/[controller]")]
[ApiController]
public class ComponentController : ControllerBase
{
private readonly ITracer tracer;
public ComponentController(ITracer tracer)
{
this.tracer = tracer;
}
/// <summary>
/// Get component by ID
/// </summary>
/// <returns></returns>
[HttpGet("GetComponent")]
public ActionResult<ComponentModel> GetComponent(string id)
{
var builder = tracer.BuildSpan("operationName");
var span = builder.Start();
// Set some context data
span.Log("Getting data");
span.SetTag(Tags.SpanKind, "Getting data request");
span.Finish();
ComponentModel component = ComponentManager.GetComponent(id);
return component;
}
}
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// Use "OpenTracing.Contrib.NetCore" to automatically generate spans for ASP.NET Core, Entity Framework Core, ...
// See https://github.com/opentracing-contrib/csharp-netcore for details.
services.AddOpenTracing();
//Init tracer
services.AddSingleton<ITracer>(t => MyTracer.InitTracer());
services.AddHealthChecks();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
But this is not working at all. What am I missing here to get it work with a remote server?
Iv finally found the solution. It seemed to have to do with how the reporter is started up. Anyhow, I changed my tracer class to this.
public static class MyTracer
{
public static ITracer tracer = null;
public static ITracer InitTracer(IServiceProvider serviceProvider)
{
string serviceName = serviceProvider.GetRequiredService<IHostingEnvironment>().ApplicationName;
Environment.SetEnvironmentVariable("JAEGER_SERVICE_NAME", "my-store");
//Environment.SetEnvironmentVariable("JAEGER_AGENT_HOST", "192.168.2.27");
//Environment.SetEnvironmentVariable("JAEGER_AGENT_PORT", "6831");
//Environment.SetEnvironmentVariable("JAEGER_SAMPLER_TYPE", "const");
//Environment.SetEnvironmentVariable("JAEGER_REPORTER_LOG_SPANS", "false");
//Environment.SetEnvironmentVariable("JAEGER_SAMPLER_PARAM","1");
//Environment.SetEnvironmentVariable("JAEGER_SAMPLER_MANAGER_HOST_PORT", "5778");
//Environment.SetEnvironmentVariable("JAEGER_REPORTER_FLUSH_INTERVAL" , "1000");
//Environment.SetEnvironmentVariable("JAEGER_REPORTER_MAX_QUEUE_SIZE" , "100");
//application - server - id = server - x
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
var sampler = new ConstSampler(sample: true);
var reporter = new RemoteReporter.Builder()
.WithLoggerFactory(loggerFactory)
.WithSender(new UdpSender("192.168.2.27", 6831, 0))
.Build();
tracer = new Tracer.Builder(serviceName)
.WithLoggerFactory(loggerFactory)
.WithSampler(sampler)
.WithReporter(reporter)
.Build();
if (!GlobalTracer.IsRegistered())
{
GlobalTracer.Register(tracer);
}
return tracer;
}
}
I know there are several inactive variables here right now. Will see if they still can be of use some how. But none is needed right now to get it rolling.
Hope this might help someone else trying to get the .NET Core working properly together with a remote Jeagertracing server.

Xunit Testing EFcore Repositories InMemory DB

I am trying to unit test the repositories, I am using InMemory option in EFCore . This is the method
[Fact]
public async Task GetCartsAsync_Returns_CartDetail()
{
ICartRepository sut = GetInMemoryCartRepository();
CartDetail cartdetail = new CartDetail()
{
CommercialServiceName = "AAA"
};
bool saved = await sut.SaveCartDetail(cartdetail);
//Assert
Assert.True(saved);
//Assert.Equal("AAA", CartDetail[0].CommercialServiceName);
//Assert.Equal("BBB", CartDetail[1].CommercialServiceName);
//Assert.Equal("ZZZ", CartDetail[2].CommercialServiceName);
}
private ICartRepository GetInMemoryCartRepository()
{
DbContextOptions<SostContext> options;
var builder = new DbContextOptionsBuilder<SostContext>();
builder.UseInMemoryDatabase($"database{Guid.NewGuid()}");
options = builder.Options;
SostContext personDataContext = new SostContext(options);
personDataContext.Database.EnsureDeleted();
personDataContext.Database.EnsureCreated();
return new CartRepository(personDataContext);
}
I am getting error which say
System.TypeLoadException : Method 'ApplyServices' in type
'Microsoft.EntityFrameworkCore.Infrastructure.Internal.InMemoryOptionsExtension' from assembly
'Microsoft.EntityFrameworkCore.InMemory, Version=1.0.1.0, Culture=neutral,
PublicKeyToken=adb9793829ddae60' does not have an implementation.
Microsoft.
EntityFrameworkCore.InMemoryDbContextOptionsExtensions.UseInMemoryDatabase(DbContextOptionsBuilder
optionsBuilder, String databaseName, Action`1 inMemoryOptionsAction)
Microsoft.EntityFrameworkCore.InMemoryDbContextOptionsExtensions.UseInMemoryDatabase[TContext]
(DbContextOptionsBuilder`1 optionsBuilder, String databaseName, Action`1 inMemoryOptionsAction)
My reference is from https://www.carlrippon.com/testing-ef-core-repositories-with-xunit-and-an-in-memory-db/
Please suggest me where i am going wrong with the current implementation . Thanks in Advance
I suggest reading the official Microsoft documentation about integration testing.
https://learn.microsoft.com/fr-fr/aspnet/core/test/integration-tests?view=aspnetcore-3.0
Secondly, I you start adding this kind of boilerplate to create your tests with the memory database you will stop doing it very soon.
For integration tests, you should be near to your development configuration.
Here my configuration files and a usage in my CustomerController :
Integration Startup File
Have all think about database creation and dependency injection
public class IntegrationStartup : Startup
{
public IntegrationStartup(IConfiguration configuration) : base(configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public override void ConfigureServices(IServiceCollection services)
{
services.AddEntityFrameworkInMemoryDatabase().BuildServiceProvider();
services.AddDbContext<StreetJobContext>(options =>
{
options.UseInMemoryDatabase("InMemoryAppDb");
});
//services.InjectServices();
//here you can set your ICartRepository DI configuration
services.AddMvc(option => option.EnableEndpointRouting = false)
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddApplicationPart(Assembly.Load(new AssemblyName("StreetJob.WebApp")));
ConfigureAuthentication(services);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public override void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var serviceScopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
using (var serviceScope = serviceScopeFactory.CreateScope())
{
//Here you can add some data configuration
}
app.UseMvc();
}
The fake startup
it's quite similar to the one in the Microsoft documentation
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost.CreateDefaultBuilder(null)
.UseStartup<TStartup>();
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseSolutionRelativeContentRoot(Directory.GetCurrentDirectory());
builder.ConfigureAppConfiguration(config =>
{
config.AddConfiguration(new ConfigurationBuilder()
//custom setting file in the test project
.AddJsonFile($"integrationsettings.json")
.Build());
});
builder.ConfigureServices(services =>
{
});
}
}
The controller
public class CustomerControllerTest : IClassFixture<CustomWebApplicationFactory<IntegrationStartup>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<IntegrationStartup> _factory;
private readonly CustomerControllerInitialization _customerControllerInitialization;
public CustomerControllerTest(CustomWebApplicationFactory<IntegrationStartup> factory)
{
_factory = factory;
_client = _factory.CreateClient();
}
}
With this kind of setting, testing the integration tests are very similar to the development controller.
It's a quite good configuration for TDD Developers.

Categories

Resources