I have a AspNetCore web app that writes to EventHub and a webjob that reads from it. I'd like the telemetry from both parts of this transaction to have the same operation id in Application Insights.
So, when I'm about to send the data to EventHub I try to pull the operation id out of the TelemetryClient, e.g.
var myOperationId = MyTelemetryClient.Context.Operation.Id;
But this always gives me null. I found this article and tried using
var request.HttpContext.Items["Microsoft.ApplicationInsights.RequestTelemetry"] as RequestTelemetry;
But again null. Any pointers on a way I can extract this value when I need it?
My code looks like this:
public class Startup
{
public void ConfigureServices( IServiceCollection IServices )
{
var builder = TelemetryConfiguration.Active.TelemetryProcessorChainBuilder;
builder.Use((next) => new MyTelemetryProcessor(next));
builder.Build();
var aiOptions = new Microsoft.ApplicationInsights.AspNetCore.Extensions.ApplicationInsightsServiceOptions();
aiOptions.EnableQuickPulseMetricStream = true;
IServices.AddApplicationInsightsTelemetry( Configuration, aiOptions);
IServices.AddMvc();
IServices.AddOptions();
TelemetryClient AppTelemetry = new TelemetryClient();
AppTelemetry.InstrumentationKey = InsightsInstrumentationKey;
IServices.AddSingleton(typeof(TelemetryClient), AppTelemetry);
}
public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory )
{
app.UseApplicationInsightsRequestTelemetry();
app.UseApplicationInsightsExceptionTelemetry();
app.UseMvc();
var configuration = app.ApplicationServices.GetService<TelemetryConfiguration>();
configuration.TelemetryInitializers.Add(new MyTelemetryInitializer());
}
}
[Route("[controller]")]
public class MyController
{
private readonly TelemetryClient mTelemetryClient;
public MyController(
TelemetryClient TelemetryClientArg)
{
mTelemetryClient = TelemetryClientArg;
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]MyPostDataClass MyPostData)
{
string telemetryId = mTelemetryClient.Context.Operation.Id; // this is null
return Ok();
}
}
I did not have OperationIdTelemetryInitializer in my TelemetryConfiguration .Active.TelemetryInitializers.
But this provides me with the current operation id:
System.Diagnostics.Activity.Current.RootId
https://github.com/Microsoft/ApplicationInsights-aspnetcore/issues/504
Think I finally cracked this without creating unwanted telemetry. The following is for AspNetCore, but should translate as long as the operation id initializer is available:
var operationId = default(string);
try
{
var telemetry = new RequestTelemetry();
TelemetryConfiguration
.Active
.TelemetryInitializers
.OfType<OperationIdTelemetryInitializer>()
.Single()
.Initialize(telemetry);
operationId = telemetry.Context.Operation.Id;
}
catch { }
Asp.Net core package has an OperationIdTelemetryInitializer (see github) which tries to set this from a request. Hypothetically if you have request tracking and this telemetry initializer turned on, everything that happens in that request should have an Operation.Id set.
if that isn't working for you, you could create your own custom TelemetryInitializer to set it however you need, or, in the place where you do find it to be null, you could just set it there, and all things in that context should then have that id.
You can get this value by sending a piece of telemetry and then reading back the operation id from the telemetry object. Not elegant, but it works.
[HttpPost]
public async Task<IActionResult> Post([FromBody]MyPostDataClass MyPostData)
{
var dummyTrace = new TraceTelemetry("hello", SeverityLevel.Verbose);
mTelemetryClient.TrackTrace(dummyTrace);
string opId = dummyTrace.Context.Operation.Id;
return Ok();
}
Related
When I try to run a background task, I always create a new scope inside that task. With the update to 3+, it seems that within the new create scope, there is a reference to the original request. The following code would break on the Debugger.Break() statement:
public class TestController : Controller
{
public readonly IServiceScopeFactory ServiceScopeFactory;
public TestController(
IServiceScopeFactory serviceScopeFactory)
{
this.ServiceScopeFactory = serviceScopeFactory;
}
// GET
public IActionResult Index()
{
Task.Run(() =>
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var actionContextAccessor = scope.ServiceProvider.GetService<IActionContextAccessor>();
var actionContext = actionContextAccessor.ActionContext;
if (actionContext.ActionDescriptor != null)
Debugger.Break();
}
});
return Content("Test");
}
}
The startup looks like this:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
The problem is that httpContext is shared with the new create scope. When one of the scopes is being disposed of, it affects the other scope. For example with IUrlHelper, which results in an "IFeatureCollection has been disposed of".
For test sake, I added a test if the httpContext would be the same. And it seems it is!
public IActionResult Index()
{
// Just for testing
var originalContext = this.HttpContext;
Task.Run(() =>
{
using (var scope = ServiceScopeFactory.CreateScope())
{
// Make sure the original request was disposed
Thread.Sleep(1000);
var actionContextAccessor = scope.ServiceProvider.GetService<IActionContextAccessor>();
var actionContext = actionContextAccessor.ActionContext;
if (originalContext == actionContext.HttpContext)
Debugger.Break();
}
});
return Content("Test");
}
For me, this seems like odd behaviour, cause I would except the new scope not to have the same httpContext. It should be a NEW scope. Should the scope be created in another way?
Found solution
In my production code I use a transient ActionContext scope, which attempt to detect if it's dealing with a request, or a background scope as followed:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
.AddTransient<ActionContext>((s) => {
var actionContextAccessor = serviceProvider.GetRequiredService<IActionContextAccessor>();
var actionContext = actionContextAccessor?.ActionContext;
// Create custom actioncontext
if (actionContext == null) {
// create a manual actionContext
}
return actionContext;
});
This doesn't seem to work anymore. The solution seems to be too validate if the httpContext exist through the IHttpContextAccessor:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
.AddTransient<ActionContext>((s) => {
var currentContextAccess = serviceProvider.GetService<IHttpContextAccessor>();
if (currentContextAccess.HttpContext == null) {
// create a manual actionContext
...
return actionContext;
}
var actionContextAccessor = serviceProvider.GetRequiredService<IActionContextAccessor>();
return actionContextAccessor.ActionContext;
});
For me, this seems like odd behaviour, cause I would except the scope not to be. Should the scope be created in another way?
Why odd? IActionContextAccessor is a singleton (same as IHttpContextAccessor is), so its normal to return the same instance even inside a newly created scope.
Since you are not awaiting, the Task.Run, your request will finish before the task is finished. How do you want to access the HttpContext after the request is done? It's only valid during the request. You have to get all the required data prior to spinning up the new Task and pass the values you need to the background task.
HttpContext is only valid for the duration of the request and since you dont await it, request ends early.
And what your code does is undefined behavior, see David's Guidelines
Do not access the HttpContext from multiple threads in parallel. It is not thread safe.
Do not use the HttpContext after the request is complete
Using the sample code from Hassan Habib's Supercharging ASP.NET Core API with OData blog post, I am able to get the record count using an OData query of $count=true:
What needs to be configured to get the response object to be wrapped in an OData context so that the #odata.count property will show?
In my own ASP.NET Core web API project, I cannot get the simple $count parameter to work and I have no idea why.
With Hassan's sample code, the response JSON is wrapped in an OData context and the payload (an IEnumerable<Student> object) is in the value property of the JSON response. In my project, the OData context wrapper does not exist; my code never returns OData context, it only returns the payload object of type IEnumerable<T>:
I've also noticed that the Content-Type in the response header is application/json; odata.metadata=minimal; odata.streaming=true; charset=utf-8 in the sample project, where as it is simply application/json; charset=utf-8 in my project. I don't see any setting that controls this in either project, so I'm assuming the Microsoft.AspNetCore.Odata NuGet package is magically changing the response when it's configured properly.
My project is also using .NET Core 2.2 (Upgraded from 2.1), all the same versions of NuGet packages as Hassan's sample projects, and all the same settings in the StartUp.cs class... although my StartUp.cs is way more complicated (hence the reason I'm not posting it's content here.)
I could reproduce your issue when i use [Route("api/[controller]")]and [ApiController] with the startup.cs like below:
app.UseMvc(routeBuilder =>
{
routeBuilder.Expand().Select().Count().OrderBy().Filter();
routeBuilder.EnableDependencyInjection();
});
To fix it,be sure you have built a private method to do a handshake between your existing data models (OData model in this case) and EDM.
Here is a simple demo:
1.Controller(comment on Route attribute and ApiController attribute):
//[Route("api/[controller]")]
//[ApiController]
public class StudentsController : ControllerBase
{
private readonly WSDbContext _context;
public StudentsController(WSDbContext context)
{
_context = context;
}
// GET: api/Students
[HttpGet]
[EnableQuery()]
public IEnumerable<Student> Get()
{
return _context.Students;
}
}
//[Route("api/[controller]")]
//[ApiController]
public class SchoolsController : ControllerBase
{
private readonly WSDbContext _context;
public SchoolsController(WSDbContext context)
{
_context = context;
}
// GET: api/Schools
[HttpGet]
[EnableQuery()]
public IEnumerable<School> Get()
{
return _context.Schools;
}
2.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.AddMvcCore(action => action.EnableEndpointRouting = false);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
var connection = #"Server=(localdb)\mssqllocaldb;Database=WSDB;Trusted_Connection=True;ConnectRetryCount=0";
services.AddDbContext<WSDbContext>(options => options.UseSqlServer(connection));
services.AddOData();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc(routeBuilder =>
{
routeBuilder.Expand().Select().Count().OrderBy().Filter();
routeBuilder.MapODataServiceRoute("api", "api", GetEdmModel());
});
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Student>("Students");
builder.EntitySet<Student>("Schools");
return builder.GetEdmModel();
}
}
Just been battling this.
I found that if I request my controller at /api/Things that most of the OData options work but $count doesn't.
However, $count does work if I request the same method via /odata/Things.
In my case I wanted to extend existing Api methods with [EnableQuery] but have it include the count metadata.
I ended up extending the EnableQuery attribute to return a different reponse, it worked perfectly.
public class EnableQueryWithMetadataAttribute : EnableQueryAttribute
{
public override void OnActionExecuted(ActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
if (actionExecutedContext.Result is ObjectResult obj && obj.Value is IQueryable qry)
{
obj.Value = new ODataResponse
{
Count = actionExecutedContext.HttpContext.Request.ODataFeature().TotalCount,
Value = qry
};
}
}
public class ODataResponse
{
[JsonPropertyName("#odata.count")]
public long? Count { get; set; }
[JsonPropertyName("value")]
public IQueryable Value { get; set; }
}
}
You can just set an empty preffix route when you map OData, and you will receive OData with your request your endpoint.
routeBuilder.MapODataServiceRoute("ODataEdmModel", "", GetEdmModel());
In my case I've created a special action named $count that users the OData Filter query my collection (EF Database) and return the Count;
[HttpGet("$count")]
public async Task<int> Count(ODataQueryOptions<MyBookEntity> odataQueryOptions)
{
var queryable = this.dataContext.MyBooks;
return await odataQueryOptions.Filter
.ApplyTo(queryable, new ODataQuerySettings())
.Cast<MyBookEntity>()
.CountAsync();
}
What I have done is created a small API in a class library. This API would be used by other sites. Think of it as a standard endpoint that all of our websites will contain.
[Route("api/[controller]")]
[ApiController]
public class CustomController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
}
The above is in a class library. Now what i would like to do is be able to add this to the projects in a simple manner.
app.UseCustomAPI("/api/crap");
I am not exactly sure how i should handle routing to the api controllers in the library. I created a CustomAPIMiddleware which is able to catch that i called "/api/crap" however i am not sure how i should forward the request over to CustomController in the library
public async Task Invoke(HttpContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
PathString matched;
PathString remaining;
if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matched, out remaining))
{
PathString path = context.Request.Path;
PathString pathBase = context.Request.PathBase;
context.Request.PathBase = pathBase.Add(matched);
context.Request.Path = remaining;
try
{
await this._options.Branch(context);
}
finally
{
context.Request.PathBase = pathBase;
context.Request.Path = path;
}
path = new PathString();
pathBase = new PathString();
}
else
await this._next(context);
}
After having done that i am starting to think i may have approached this in the wrong manner and should actually be trying to add it directly to the routing tables somehow. That being said i would like it if they could customize the endpoint that the custom controller reads from.
Update
The following does work. Loading and registering API Controllers From Class Library in ASP.NET core
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddApplicationPart(Assembly.Load(new AssemblyName("WebAPI")));
However i am really looking for a middlewere type solution so that users can simply add it and i can configure the default settings or they can change some of the settings. The above example would not allow for altering the settings.
app.UseCustomAPI("/api/crap");
Update from comment without Assembly
If i dont add the .AddApplicationPart(Assembly.Load(new AssemblyName("WebAPI")));
This localhost page can’t be found No webpage was found for the web address:
https://localhost:44368/api/Custom
To customise the routing for a controller at runtime, you can use an Application Model Convention. This can be achieved with a custom implementation of IControllerModelConvention:
public class CustomControllerConvention : IControllerModelConvention
{
private readonly string newEndpoint;
public CustomControllerConvention(string newEndpoint)
{
this.newEndpoint = newEndpoint;
}
public void Apply(ControllerModel controllerModel)
{
if (controllerModel.ControllerType.AsType() != typeof(CustomController))
return;
foreach (var selectorModel in controllerModel.Selectors)
selectorModel.AttributeRouteModel.Template = newEndpoint;
}
}
This example just replaces the existing template (api/[controller]) with whatever is provided in the CustomControllerConvention constructor. The next step is to register this new convention, which can be done via the call to AddMvc. Here's an example of how that works:
services.AddMvc(o =>
{
o.Conventions.Add(new CustomControllerConvention("api/whatever"));
});
That's all that's needed to make things work here, but as you're offering this up from another assembly, I'd suggest an extension method based approach. Here's an example of that:
public static class MvcBuilderExtensions
{
public static IMvcBuilder SetCustomControllerRoute(
this IMvcBuilder mvcBuilder, string newEndpoint)
{
return mvcBuilder.AddMvcOptions(o =>
{
o.Conventions.Add(new CustomControllerConvention(newEndpoint));
});
}
}
Here's how that would be called:
services.AddMvc()
.SetCustomControllerRoute("api/whatever");
This whole approach means that without a call to SetCustomControllerRoute, api/Custom will still be used as a default.
Background
I'm trying to set up a Web API 2 which needs to communicate to a NServicebus Endpoint.
I will need to implement IoC, which will be done using Autofac.
What I have
A controller defined like so:
[RoutePrefix("api")]
public class Controller : ApiController
{
private IEndpointInstance EndpointInstance { get; set; }
public public MyController(IEndpointInstance endpointInstance)
{
this.EndpointInstance = endpointInstance;
}
[HttpGet]
[Route("dostuff")]
public async Task DoStuff()
{
var command = new MyCommand
{
...
};
await this.EndpointInstance.SendLocal(command);
}
}
And in global.asax
Application_Start
protected async void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
await RegisterNServiceBusWithAutofac();
}
RegisterNServiceBusWithAutofac
private async Task RegisterNServiceBusWithAutofac()
{
var builder = new ContainerBuilder();
var endpointConfiguration = await GetEndpointConfiguration("My.Service");
var endpointInstance = await Endpoint.Start(endpointConfiguration);
builder.RegisterInstance(endpointInstance);
var container = builder.Build();
endpointConfiguration.UseContainer<AutofacBuilder>(c => c.ExistingLifetimeScope(container));
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
}
GetEndpointConfiguration
private static async Task<EndpointConfiguration> GetEndpointConfiguration(string name)
{
var endpointConfiguration = new EndpointConfiguration(name);
// Set transport.
var routing = endpointConfiguration.UseTransport<MsmqTransport>().Routing();
// Register publish to self
routing.RegisterPublisher(typeof(EventHasFinished), name);
endpointConfiguration.UseSerialization<JsonSerializer>();
endpointConfiguration.UsePersistence<InMemoryPersistence>();
endpointConfiguration.SendFailedMessagesTo("error");
endpointConfiguration.EnableInstallers();
return endpointConfiguration;
}
The result
I get the following error on the UseContainer line:
Unable to set the value for key:
NServiceBus.AutofacBuilder+LifetimeScopeHolder. The settings has been
locked for modifications. Move any configuration code earlier in the
configuration pipeline
What I think this means
I think I need to do all Autofac registrations for the NServicebus when creating the endpointConfiguration. The above manipulates the builder instance after that.
But
I can't do the above, because I need to register the endpointinstance to the IoC, because I need that in my controller to send messages. And that doesn't exist yet, because I need the endpointConfiguration first, for that.
So I have a chicken and egg situation ...
Question
Do I understand the issue correctly and how can I solve it while
making sure that IoC works correctly for the Controller?
I.e.: this.EndpointInstance has been correctly instantiated through IoC.
Instead of registering the actual instance, you could register it with a lambda expression that is going to be executed the first time the container will be asked to resolve IEndpointInstance.
builder
.Register(x =>
{
var endpointConfiguration = GetEndpointConfiguration("My.Service").GetAwaiter().GetResult();
var endpointInstance = Endpoint.Start(endpointConfiguration).GetAwaiter().GetResult();
return endpointInstance
})
.As<IEndpointInstance>()
.SingleInstance();
I have a self hosted Owin application that uses Nancy. In one of the NancyModules I need to get an instance of IOwinContext.
This question touches on the subject, but there's no solution in it: Get current owin context in self host mode
It says that for Nancy, you have to use NancyContext to get to the Items dictionary and look for the value corresponding to the key "OWIN_REQUEST_ENVIRONMENT".
I do have access to the NancyContext and I can see the Items dictionary and that it contains a key called "OWIN_REQUEST_ENVIRONMENT". (I could also call the NancyContext.GetOwinEnvironment() extension, which gives the same result
However, when I get that key it doesn't contain an actual IOwinContext.
It contains a lot of keys with information about Owin (some of the keys are owin.RequestPath, owin.RequestMethod, owin.CallCancelled, and more), but not an actual context object. And it is only really a dictionary with various keys, so I can't cast it to an IOwinContext either.
How can I get from a NancyContext to an IOwinContext object?
public class MyStartup
{
public void Start()
{
var options = new StartOptions()
options.Urls.Add(new Uri("http://*:8084"));
options.AppStartup(this.GetType().AssemblyQualifiedName;
var host = WebApp.Start(options, Configuration);
}
public void Configuration(IAppBuilder app)
{
app.UseNancy();
}
}
public class MyModule : NancyModule
{
Get["/", true] = async(x, ct) =>
{
var owinEnvironment = Context.GetOwinEnvironment();
// Now what?
}
}
var owinContext = new OwinContext(Context.GetOwinEnvironment());
example:
public class SecurityApi : NancyModule
{
public SecurityApi()
{
Post["api/admin/register", true] = async (_, ct) =>
{
var body = this.Bind<RegisterUserBody>();
var owinContext = new OwinContext(Context.GetOwinEnvironment());
var userManager = owinContext.GetUserManager<ApplicationUserManager>();
var user = new User {Id = Guid.NewGuid().ToString(), UserName = body.UserName, Email = body.Email};
var result = await userManager.CreateAsync(user, body.Password);
if (!result.Succeeded)
{
return this.BadRequest(string.Join(Environment.NewLine, result.Errors));
}
return HttpStatusCode.OK;
};
}
}
Actually, question that you mentioned has some tips that you probably missed.
For Nancy, you have to use NancyContext to get to the Items dictionary
and look for the value corresponding to the key
"OWIN_REQUEST_ENVIRONMENT". For SignalR, Environment property of
IRequest gives you access to OWIN environment. Once you have the OWIN
environment, you can create a new OwinContext using the environment.
So, once you called var owinEnvironment = Context.GetOwinEnvironment() and got the dictionary then you can create OwinContext (which is just wrapper for these dictionary values)
It has a constructor OwinContext(IDictionary<String, Object>) which, i guess, is what you need.
Also, you can get OwinContext from HttpContext:
// get owin context
var owinContext = HttpContext.Current.GetOwinContext();
// get user manager
var userManager = owinContext.GetUserManager<YourUserManager>();
I ended up solving this by creating new Owin middleware. In the middleware you have access to the current Owin context, which gives you access to the Owin environment.
When you have access to the Owin environment it's simply a case of adding the Owin context to the environment. When the context is in the environment you can retrieve it in the NancyModule.
After retrieving it like this I also had access to the GetUserManager() method on the context so that I could get my AspNetIdentity manager (as I mentioned in a comment to another answer). Just remember that the middleware must be added before Nancy to the Owin pipeline.
Startup
public class Startup
{
public void Start()
{
var options = new StartOptions()
options.Urls.Add(new Uri("http://*:8084"));
options.AppStartup(this.GetType().AssemblyQualifiedName;
var host = WebApp.Start(options, Configuration);
}
public void Configuration(IAppBuilder app)
{
app.Use(typeof(OwinContextMiddleware));
app.UseNancy();
}
}
Middleware
public class OwinContextMiddleware : OwinMiddleware
{
public OwinContextMiddleware(OwinMiddleware next)
: base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
context.Environment.Add("Context", context);
await Next.Invoke(context);
}
}
NancyModule
public class MyModule : NancyModule
{
public MyModule()
{
Get["/", true] = async(x, ct) =>
{
IDictionary<string, object> environment = Context.GetOwinEnvironment();
IOwinContext context = (IOwinContext)environment["Context"]; // The same "Context" as added in the Middleware
}
}
Caveat
The middleware listed above is untested as the middleware I have is more complex and I haven't had the time to create a working example. I found a simple overview on how to create Owin middleware on this page.