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.
Related
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();
}
My goal is to make the communication between two applications (WebAPI and Worker) via MassTransit's Request/Response technique. The problem is that I'm never getting inside the consumer (request client), I'm getting a timeout instead.
I found a similar question already but the answer included a link to a github repository which no longer exists. I also tried following a sample but for some reason most samples are created as console applications which is useless for me since I have two WebAPIs trying to communicate with each other.
Anyway, here's my code:
WebAPI.Startup
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMassTransit(massTransitConfig =>
{
massTransitConfig.UsingAzureServiceBus((ctx, cfg) =>
{
cfg.Host("Endpoint=sb://----.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=----");
});
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
Worker.Startup
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMassTransit(massTransitConfig =>
{
massTransitConfig.UsingAzureServiceBus((ctx, cfg) =>
{
cfg.Host("Endpoint=sb://----.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=----");
});
massTransitConfig.AddConsumer<CreateScheduleRequestClient>();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
WebAPI.RequestController
[Route("api/requests")]
public class RequestsController : ControllerBase
{
private readonly IBus _bus;
public RequestsController(IBus bus)
{
_bus = bus;
}
[HttpPost("create-schedule")]
public async Task<IActionResult> CreateSchedule()
{
var client = _bus.CreateRequestClient<CreateScheduleRequest>();
var response = await client.GetResponse<ScheduleCreatedResponse>(new CreateScheduleRequest());
return Ok(response.Message.Succeeded);
}
}
DataTransferObjects.CreateScheduleRequest
public class CreateScheduleRequest
{
public string CommandName { get; set; }
public string Cron { get; set; }
}
Worker.RequestClients.CreateScheduleRequestClient
public class CreateScheduleRequestClient : IConsumer<CreateScheduleRequest>
{
public async Task Consume(ConsumeContext<CreateScheduleRequest> context)
{
await context.RespondAsync(new ScheduleCreatedResponse(true));
}
}
DataTransferObjects.ScheduleCreatedResponse
public class ScheduleCreatedResponse
{
public bool Succeeded { get; }
public ScheduleCreatedResponse(bool succeeded)
{
Succeeded = succeeded;
}
}
When I call the only endpoint in the RequestsController, I'm getting MassTransit.RequestTimeoutException: Timeout waiting for response, RequestId: 27f60000-167b-00ff-ea0f-08d8e3a0832e after a short period. I'm not able to verify much more about it, I thought it outght to work out of the box but perhaps I'm missing some parameters when initializing the bus in my Startup classes?
================EDIT================
I changed my code a little with regards to what Chris Patterson suggested and to specify that I'd like to go with the IBus approach. The code still throws the same exception after the change.
First, I'd suggest reviewing the request documentation, in particular the controller example that injects IRequestClient<T> into the controller. That will fix your controller code, which shouldn't be using IBus.
Second, your response should be an actual message type, it can't be true. You need to create a second message contract, such as ScheduleRequestCreated and respond with that message type. Then, your GetResponse would change to
GetResponse<ScheduleRequestCreated>(new CreateScheduleRequest(...))
And your response would be:
RespondAsync(new ScheduleRequestCreated(...))
I already have been trying to solve this for days, I created a Web API application (using .NET 5, VS2019) and when I was finally be able to run it without errors in the IIS, I receive 404 errors in the responses, simply put the controller code is never called (never hit by the debugger) with no exceptions or any other errors, just this screen (calling the first route in the controller):
This localhost page can’t be found
No webpage was found for the web address:
https://localhost:44342/sicrestweb/sync/gs/usuarios?json_data={fields:*,%20database:"Provider=Microsoft.ACE.OLEDB.12.0;Data%20Source=F:\VBDev\Sicrest3.4\DPWT314.mdb;%20Persist%20Security%20Info=False;"}
Here is the Program code:
public static void Main(string[] args)
{
CD = Directory.GetCurrentDirectory();
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
The Startup code:
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();
services.AddScoped<ISyncService, SyncService>();
}
// 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(); });
}
}
The controller code (fragment) :
[ApiController]
[Route("SicrestWeb/[controller]")]
public class SyncController : ControllerBase // Controller
{
private readonly ISyncService ISS;
private SyncService SS { get { return (SyncService) ISS; } }
public ActionResult HTTPResponse { get { return SS.HTTPResponse; } } // HTTP Response
public String CurrentRoute { get { return (Request != null && Request.Path != null) ? (Request.Path.Value ?? TestRoute) :TestRoute; } } // Get route string
public String TestRoute { get; set; }
public SyncController(ISyncService isys) { ISS = isys; TestRoute = ""; }
[HttpGet]
[Route("GS/{table_name}/{json_data}/{key=''}")]
[Route("GSI/{table_name}/{json_data}/{key=''}")]
public ActionResult Select(String table_name, String json_data, String key="") // (GS) returns data as requested
{
// process request
if (!ISS.Dispatch(CurrentRoute, table_name, json_data, key))
{
// Handles error
}
// Generate response
return HTTPResponse;
}
.
.
.
}
The only things I am sure is the controller code works when is called directly (it is already unit tested), but the address/routing mechanism never calls it, like the controller and its routes are not identified thus are never called.
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.
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.