Application Insights - How to add custom metric to your request information? - c#

This question is very similar to what I want to know. I've got a web api service on an azure cloud service with Application Insights configured. On the request information portal, that is generated automatically, I want to add a custom http header that's a part of the request into the information that is being logged with each request. The question is how do I do this?
I've tried using a telemetry initializer like below, but this fails (as in I don't see the information on the portal). I also added this in the global.asax
TelemetryConfiguration.Active.TelemetryInitializers.Add(propertyTelemetryInitializer);
public class PropertyTelemetryInitializer : ITelemetryInitializer
{
private readonly HttpContext httpContext;
public PropertyTelemetryInitializer(HttpContext httpContext)
{
this.httpContext = httpContext;
}
public void Initialize(ITelemetry telemetry)
{
this.AddTelemetryContextPropertFromContextHeader(telemetry, "xyz");
this.AddTelemetryContextPropertFromContextHeader(telemetry, "abc");
this.AddTelemetryContextPropertFromContextHeader(telemetry, "123");
}
private void AddTelemetryContextPropertFromContextHeader(ITelemetry telemetry, string headerKey)
{
var requestTelemetry = telemetry as RequestTelemetry;
telemetry.Context.Properties[headerKey] = this.httpContext.Request.Headers[headerKey] ?? string.Empty;
telemetry.Context.Properties[headerKey] = this.httpContext.Request.Headers[headerKey] ?? string.Empty;
}
}
Also is there a way to do this from the controller method itself? Something similar to the below (note: the below does not work)?
[Route("api/Something")]
[HttpGet]
[ResponseType(typeof(Something))]
public async Task<Something> GetSomething()
{
var requestTelemetry = new RequestTelemetry();
this.AddCustomHeadersToRequestTelemetry(requestTelemetry);
var result = await this.Service.GetSomethingAsync();
requestTelemetry.Properties["result"] = result.ToString();
return TypeMapper.Map<Model.Something, Something>(result);
}
/// <summary>
/// Adds the custom headers to request telemetry.
/// </summary>
/// <param name="controller">The controller.</param>
/// <param name="requestTelemetry">The request telemetry.</param>
public static void AddCustomHeadersToRequestTelemetry(this ApiController controller, RequestTelemetry requestTelemetry)
{
if (controller == null)
{
throw new ArgumentNullException("controller");
}
if (requestTelemetry == null)
{
throw new ArgumentNullException("requestTelemetry");
}
requestTelemetry.Context.Properties["abc"] = controller.Request.GetABCFromHeader();
requestTelemetry.Context.Properties["xyz"] = controller.Request.GetXYZFromHeader();
requestTelemetry.Context.Properties["123"] = controller.Request.Get123FromHeader();
}

Using TelemetryInitializers is the right solution. Some comments:
var requestTelemetry = telemetry as RequestTelemetry;: you do not use requestTelemetry after that. I guess you wanted to check for null.
Adding telemetry initializer in the Active configuration should be fine. You can also consider moving it to the applicationinsights.config
Custom properties do not show up in the portal immediately. Have you tried to reopen IE after some time and check your request again?
Can you debug? Do you see that you get in your tememetry initializer? Do you see any AI specific traces in search?
Regarding your second question. Right now telemetry initializers are the only (official) way to get to the autogenerated RequestTelemetry (which is actually in the HttpContext). There are plans to make most of the classes in web public and eventually open source it. But there is no ETA yet. If you create and track request yourself you can add custom properties as you mentioned.
UPDATE: Starting from 2.0.0-beta3 autogenerated request telemetry is accessible though HttpContext extension method: System.Web.HttpContextExtension.GetRequestTelemetry

Related

Scoped service added is producing null error over the same request within middleware

I'm creating a piece of custom middleware that is going to used for telemetry purposes. This middleware is going to have a service that it uses to keep track of the different logs generated by my API, and once the request response is triggered it'll send off the traces to AppInsights.
The Invoke method of the middleware looks like this:
public async Task InvokeAsync(HttpContext context)
{
_telemetryService.InitNewEvent();
_telemetryService.Add("path", context.Request.Path, null); // Add works here
await _next(context);
_telemetryService.Add("statusCode", context.Response.StatusCode, null); // Add throws null error here
_telemetryService.SendEvent(context.Response);
}
Telemetry Service Excerpt
public class TelemetryService
{
private TelemetryClient _telemetryClient;
private List<TelemetryTrace> _currentTelemetryEventData;
private Dictionary<string, string> _finalisedTelemetryEventData;
private string _eventName;
private string _apiName = "pmapi";
public TelemetryService(TelemetryClient telemetryClient)
{
_telemetryClient = telemetryClient;
}
public void InitNewEvent()
{
_currentTelemetryEventData = new List<TelemetryTrace>();
Console.WriteLine("New telemetry event inited");
}
public void Add(string key, object data, SeverityLevel? severityLevel)
{
_currentTelemetryEventData.Add(new TelemetryTrace
{
Key = key,
Data = data,
SeverityLevel = severityLevel
});
}
}
A new event, which is just a dictionary, is inited by the middleware and a log it added to it. The middleware then awaits the response from the pipeline, during this time the API controller will add other logs to the event, and then the status code is added to the event and the event is sent when the pipeline returns to the middleware.
However I've noticed that if I add the telemetryService as a scoped or transient service the dictionary that holds the log data is set back to null after await _next(context) has been called.
This makes sense for transient, but with the definition of scoped being Scoped objects are the same within a request, but different across different requests. I expected that my dictionary state would be kept. But that only occurs with a singleton. Why isn't this happening the way I'm expecting?

Is it thread safe to create separate instance of dependent component in case of web api2 using structuremap

I have a web api 2 controller: TestController.cs and an action filter: TestAuthorizeAttribute.cs
I am using StructureMap.WebApi2 nuget package for Web API 2 project for setting the dependency injection.
I am trying to create instance of TestService object in both TestController.cs and TestAuthorizeAttribute.cs.
Is this the correct approach to create instance of TestService.
Is it possible that the multiple threads seem to refer to Web API handling multiple simultaneous requests that are somehow handled by the same DataContext
Please help me to know are there any issues with the below mentioned code.
[RoutePrefix("api/test")]
public class TestController : ApiController
{
public TestController(ITestService testService)
{
_testService = testService;
}
/// <summary>
/// Get details of individual test
/// </summary>
/// <param name="Id"> Id</param>
/// <param name="selectedSection">Selected Section</param>
/// <returns>Details of the Test</returns>
[Route("{Id:int}/{selectedSection?}", Name = "TestDetails")]
[HttpGet]
[TestAuthorize]
public HttpResponseMessage Get(int Id, string selectedSection = "")
{
var testDetails = _testService.GetTestResults(Id);
if (scan != null)
{
var progress = _testService.GetProgress(scan, user);
return Request.CreateResponse(HttpStatusCode.OK, scanDetails);
}
else
{
return Request.CreateResponse(HttpStatusCode.NotFound, new { error = GlobalConstants.ERROR_REVIEW_NOTFOUND });
}
}
}
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public class TestAuthorizeAttribute : ActionFilterAttribute
{
ITestService testService;
public ScanAuthorizeAttribute()
{
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
_testService = actionContext.Request.GetDependencyScope().GetService(typeof(ITestService)) as ITestService;
var Id = Convert.ToInt32(actionContext.ActionArguments["Id"]);
var testDetails = _testService.GetTestResults(Id);
}
What you're doing looks pretty much spot on. This is pretty much exactly what you want to do.
A few things to note:
Assuming the ITestService is TransientScoped (the default) your Filter and your Controller will actually use the same instance of the ITestService.
If your DataContext is TransientScoped it will be unique per request (since the NestedContainer is stored on the request via the DependencyScope) so you should not see a race condition you were worried about.
There are a few caveats to this that I know of. One of which is ModelBinders and ModelBinderProviders are instantiated with this method from System.Web.Http.ModelBinding.ModelBinderAttribute:
private static object GetOrInstantiate(HttpConfiguration configuration, Type type)
{
return configuration.DependencyResolver.GetService(type) ?? Activator.CreateInstance(type);
}
This method does NOT use a DependencyScope so any DBContext used here will be completely unique (TransientScoped object from non-Nested container.)
I have seen really weird race conditions when dealing with IEnumerable dependencies in webapi. The default IEnumerables used by StructureMap do not appear to be thread safe.
Be careful with data access in Filters, Delegating Handlers, Model Binders, etc. Introducing a N+1 or otherwise costly query in these layers can really hurt since they get called for every request they handle.
Any other questions about this stuff please ask, I've done a ton of stuff in this area lately so I have a pretty decent understanding of all of it.

Accessing a DbContext dynamically

I have an application used to display location data to a user. This data can come from multiple sources (e.g. a WebApi or a LocalDatabase). Therefore I have a repository which manages all the data from these sources.
Each repository implements the following interface
interface IRepository<TKey, TValue> : IDiscoverable, ILocalizable
{
IDictionary<TKey, TValue> FindAll();
TValue Find(TKey identifier);
// Additional methods left out.
}
A concrete implementation (in this case for a WebApi request) then creates a request which gets executed by a ServiceClient. This service client returns a response to the Repository which passes said response to a converter that converts the data into a format used by the application.
Now my problem is the Request part. Not for the WebApi, but for database requests.
I currently have one DbContext for the Locations database, but more will follow when the application gets extended. And herein lies the crux: Writing a ServiceClient for Api requests is easy, since I only have to query a webpage and fetch the JSON. The URL can therefore be built by joining multiple strings. So a Request can look like this:
public interface IRequest
{
/// <summary>Gets the resource path.</summary>
string Resource { get; }
/// <summary>Gets the request parameters.</summary>
/// <returns>A collection of parameters.</returns>
IEnumerable<KeyValuePair<string, string>> GetParameters();
/// <summary>Gets additional path segments for the targeted resource.</summary>
/// <returns>A collection of path segments.</returns>
IEnumerable<string> GetPathSegments();
}
How can I achieve a similar approach with multiple DbContext?
I understand that I have to pass the instance of the DbContext I want to query to the service manager. But how can I query the DbSet properties without writing a Service manager for each DbContext?
Is this eve possible, or would I have to scrap my design altogether?
Here are some additional information, This is my current repository implementation. I commented out the code used to query a web api. But I'd like my database querying code to look almost the same.
To clarify: explicit interface implementation is the method the rest of the program calls to get the data. The user is able to switch out the type of repository, thus giving the ability to choose from different data sources.
Inside the method I'll generate a request. If the data source is a web api the request holds information to build an URL to the api endpoint (the Url is built and queried by the ServiceClient). The ServiceClient then returns a response implementing the IResponse interface (see below). The response then is passed to a converter which converts the response to POCOs used by the rest of the application.
What I want to do: Build a request class that allows me to dynamically select the DbContext used to access the database. For that I need a way to pass the DbContext inside the Request, so the ServiceClient can use it. I further need to pass which properties to use so I can get the data, since not every endpoint has the same Properties.
Here is the code I have so fat:
public interface IResponse<T> : ILocalizable
{
/// <summary>Gets or sets the response content.</summary>
T Content { get; set; }
/// <summary>Gets or sets the <see cref="DateTimeOffset"/> at which the message originated..</summary>
DateTimeOffset Date { get; set; }
/// <summary>Gets or sets a collection of custom response headers.</summary>
IDictionary<string, string> ExtensionData { get; set; }
}
public interface ILocationRepository : IRepository<Guid, Location>
{
}
public class LocationRepository : ILocationRepository
{
private readonly LocationContext context;
private readonly IConverter<IResponse<ICollection<LocationDataContract>>, IDictionary<Guid, Location>> responseConverter;
public LocationRepository()
{
this.context = new LocationContext();
var locationConverter = new LocationConverter();
this.responseConverter = new DictionaryResponseConverter<LocationDataContract, Guid, Location>(locationConverter, location => location.Id);
}
IDictionary<Guid, Location> IRepository<Guid, Location>.FindAll()
{
// This is how the request against a web api looks like.
// This works without a doubt and totally flawless.
// IItemRepository self = this;
// var request = new ItemDetailsRequest
// {
// Identifier = identifier.ToString(NumberFormatInfo.InvariantInfo),
// Culture = self.Culture
// };
// var response = this.serviceClient.Send<ItemDataContract>(request);
var locations = this.context.Locations.ToList();
var cityDetails = this.context.CityDetails.ToList();
var planetDetails = this.context.PlanetDetails.ToList();
var systemDetails = this.context.SystemDetails.ToList();
IEnumerable<LocationsDetails> details = cityDetails.Cast<LocationsDetails>().Concat(planetDetails).Concat(systemDetails).ToList();
foreach (var location in locations)
{
location.Details = details.SingleOrDefault(detail => detail.Id == location.Id);
}
var response = new Response<ICollection<LocationDataContract>>
{
Content = locations
};
var service = new DatabaseServiceClient<LocationContext>();
return this.responseConverter.Convert(response);
}
Location IRepository<Guid, Location>.Find(Guid identifier)
{
throw new NotImplementedException();
}
}

Owin startup class and url

I have an application that supports subdomains. Each subdomain represents a company and thus each subdomain potentially looks and feels like an extension of their own website.
This is done using a companyId which is obtained by this method:
/// <summary>
/// Get our company based from the URI host
/// </summary>
/// <returns>A company</returns>
public Company GetTenant()
{
var host = ConfigurationManager.AppSettings["domain"];
var currentHost = HttpContext.Current.Request.Headers["HOST"];
var defaultUri = GetUriFromUrl(host);
var currentUri = GetUriFromUrl(currentHost);
foreach (var company in this.GetAll("Settings"))
if (CheckCompanyByUri(company, currentUri))
return company;
if (!currentUri.IsLoopback && !CompareUrls(currentUri, defaultUri))
throw new Exception("The URL you have specified is not in our systems: " + currentUri.Host);
return null;
}
So, I have now built an api and want to use OAuthAuthorizationServerOptions but the problem is that the Users for each company are different and are obtained by using the CompanyId.
static Startup()
{
using (var uow = new UnitOfWork<SkipstoneContext>())
{
var service = new CompanyService(uow, null);
var company = service.GetTenant(); // HttpContext is not available at this point (so getting the url is impossible)
if (company != null)
{
var companyId = company.Id;
UserService = new UserService(uow, null, companyId);
PublicClientId = companyId;
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserService),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
}
}
I don't have access to HttpContext from the Startup class, so is there anyway I can actually get access to the current requested URL from startup?
This isn't available at startup. You will either need to setup separate Virtual Directories in IIS (if you're using it) and have actual different apps handling each virtual directory.
Otherwise you will need to filter on each individual request (most webframeworks have some kind of routing engine for this).
Startup runs, as its name points out, on startup of your application. It's the entry point of the web application. It sets up the stack of middleware, including your application as the last item in the stack.
This means, there is not necessarily any request at that stage.
What you want is to write a middleware that you will plug at the begining of the OWIN pipeline.
In that middleware, you can intecept the request, analyze any parameters you want, and redirect your user before you hit any of you application code.
Is that of any help?
EDIT
pseudo code example :
public class MyMiddleware : OwinMiddleware
{
public MyMiddleware(OwinMiddleware next)
: base(next) { }
public override Task Invoke(IOwinContext context)
{
//Analyze the request.
//find the client id
//RedirectToClientSpecificUrl?
}
}
in startup :
app.Use<MyMiddleware>();
But you will probably find more relevant examples with some quick google fu :
Here he deals with authentication, and has a redirect in the middleware :
http://blog.tomasjansson.com/owin-middleware/

ASP.Net Web Api - ApiExplorer does not contain any ApiDescriptions

I am trying to implement an Options method in a controller of my web service that will return a message containing the valid HTTP methods for the URI endpoint associated with the controller. My Options method looks something like this:
public HttpResponseMessage Options()
{
var resp = new HttpResponseMessage();
resp.Content = new StringContent("");
var apiExplorer = GlobalConfiguration.Configuration.Services
.GetApiExplorer();
foreach (ApiDescription api in apiExplorer.ApiDescriptions)
{
resp.Content.Headers.Add("Allow", api.HttpMethod.Method);
}
return resp;
}
I have tried the above method in a brand-new Web Api project (implication: unaltered routing) inside of a controller with Get, Post, and Delete methods. As expected, a response with "Allow: GET, POST, DELETE" is returned. I am having trouble, however, adding this to a larger project that I am working on. In the larger project, the ApiDescriptions list within ApiExplorer does not contain any elements. Why is this? I suspect it is due to the custom routing that has been implemented, although the only basis for that suspicion is the following link:
http://forums.asp.net/t/1821651.aspx/1
Has anybody else experienced this empty ApiDescription list? If so, did you find a remedy?
Note: I am using MCV 4 RC
If you use Glimpse, you might have to disable it's route inspector:
<glimpse defaultRuntimePolicy="On" endpointBaseUri="~/Glimpse.axd">
<logging level="Off" />
<tabs>
<ignoredTypes>
<add type="Glimpse.AspNet.Tab.Routes, Glimpse.AspNet" />
</ignoredTypes>
</tabs>
<inspectors>
<ignoredTypes>
<add type="Glimpse.AspNet.Inspector.RoutesInspector, Glimpse.AspNet" />
</ignoredTypes>
</inspectors>
</glimpse>
Glimpse creates RouteProxies that break enumeration in HostedHttpRouteCollection:
https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Http.WebHost/Routing/HostedHttpRouteCollection.cs
I know the link is for mono but the same is true for standard .Net.
You should look to upgrade to the RTM of WebApi that was released yesterday and then check out the newly released ASP.NET WebApi Help Page (Preview) that was also released yesterday.
This package automatically generates help page content for Web APIs on your site. Visitors to your help page can use this content to learn how to call your web APIs. Everything generated by the help page is fully customizable using ASP.NET MVC and Razor.
It is implementing the ApiExplorer under the covers.
The solution for this problem is to comment in ProjectName\Areas\HelpPage\Controllers\HelpController.cs the constructors like this:
public class HelpController : Controller
{
private const string ErrorViewName = "Error";
// public HelpController()
// : this(GlobalConfiguration.Configuration)
// {
// }
// public HelpController(HttpConfiguration config)
// {
// Configuration = config;
// }
/// <summary>
/// GlobalConfiguration By default
/// </summary>
protected static HttpConfiguration Configuration
{
get { return GlobalConfiguration.Configuration; }
}
public ActionResult Index()
{
ViewBag.DocumentationProvider = Configuration.Services.GetDocumentationProvider();
return View(Configuration.Services.GetApiExplorer().ApiDescriptions);
}
....
The default constructor is not called;
The second method is to inject the default constructor by add this attribute [InjectionConstructor] on default constructor like this:
public class HelpController : Controller
{
private const string ErrorViewName = "Error";
[InjectionConstructor]
public HelpController()
: this(GlobalConfiguration.Configuration)
{
}
public HelpController(HttpConfiguration config)
{
Configuration = config;
}
/// <summary>
/// GlobalConfiguration By default
/// </summary>
protected static HttpConfiguration Configuration { get; private set; }
....

Categories

Resources