WCF service operation name - azure-application-insights

Is there a way to configure App Insights to collect the operation name when monitoring a WCF service? All requests get lumped together by URL (which are just POSTs that end in .svc), so there is no easy way to determine which particular operation was called on the service.
Does there need to be a custom Telemetry Initializer that can somehow determine which operation was actually called and set a custom property? if so, how do you determine the current WCF operation name?

Another option for collecting data on WCF operations is to use the Microsoft.ApplicationInsights.Wcf Nuget package. You can read more about this here.

Brett,
Operation name can be customized in two ways:
1) Using a custom telemetry initializer - that specifically sets operation name.
For more information about telemetry initializers: Custom Telemetry Initializers
2) From sdk version 2-beta3, auto-generated request telemetry is accessible though HttpContext extension method:
System.Web.HttpContextExtension.GetRequestTelemetry
Once the request telemetry is retrieved, operation name associated with it can be changed.
Please let me know if this addressed your question.
Thanks,
Karthik

If you want to get the name of the WCF method called from a client in application insight you can use the following ITelemetryInitializer
With .net 5.0, the httprequest object is stored in the raw object properties of the telemetry context.
public class SoapActionHeaderTelemetryInitializer : ITelemetryInitializer
{
private static readonly Regex _soapActionUri = new Regex("^\"(?<uri>.*)\"$", RegexOptions.Compiled);
public void Initialize(ITelemetry telemetry)
{
DependencyTelemetry httpDependency = telemetry as DependencyTelemetry;
if (httpDependency != null)
{
httpDependency.Context.TryGetRawObject("HttpRequest", out var request);
if (request is HttpRequestMessage httpRequest)
{
if (httpRequest.Headers.TryGetValues("SOAPAction", out var values) && values.Any())
{
// SOAP Action is contained within quote : https://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383528
var soapAction = _soapActionUri.Match(values.First()).Groups["uri"].Value;
telemetry.Context.GlobalProperties["SOAPAction"] = soapAction;
}
}
}
}
}

Related

What is the easiest way to add custom dimensions to default Request Telemetry for App service?

I just leverage default Application Insights logging to log the request telemetry without ANY custom code.
The request telemetry looks like this:
timestamp [UTC] 2019-12-19T00:22:10.2563938Z
id |a758472d124b6e4688a33b2ad9755f33.b3979544_
name GET MyMethod [type]
url https://xxxx
success True
resultCode 200
duration 153.2676
performanceBucket <250ms
itemType request
customDimensions
AppId xxxx-xxxx-xxxx-xxxx-xxxxxx
AspNetCoreEnvironment: west us
_MS.ProcessedByMetricExtractors (Name:'Requests', Ver:'1.1')
Now I want to add a new property to customDimensions in Request telemetry, say, correlationId. What is the easiest way to to it? I just want to expend the existing request telemetry, don't want to create new event.
If you're interested in massaging data (i.e. modify based on what's available in telemetry item itself) then Ivan's answer is the right one.
If you'd like to add something to existing request then you need to do a few things:
1) Use Activity.Tags property bag while in a request
Activity.Current?.AddTag("TagName", "TagValue");
2) Have Telemetry initializer which puts tags as custom dimensions (in next versions we might add it as default initializer and this step will no longer be required)
/// <summary>
/// Helper class to workaround AI SDK not collecting activity tags by default.
/// This initializer allows the business logic to have no AI references.
/// </summary>
public class ActivityTagsTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
var activity = Activity.Current;
var requestTelemetry = telemetry as ISupportProperties;
if (requestTelemetry == null || activity == null) return;
foreach (var tag in activity.Tags)
{
requestTelemetry.Properties[tag.Key] = tag.Value;
}
}
}
3) Register in Startup
services.AddSingleton<ITelemetryInitializer, ActivityTagsTelemetryInitializer>();
For adding custom dimensions, you can take use of ITelemetryInitializer.
Here is an example for a .NET core web project:
1.Add a class named MyTelemetryInitializer to the project, and the code like below:
public class MyTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
//if it's not a request, just return.
if (requestTelemetry == null) return;
if (!requestTelemetry.Properties.ContainsKey("correlationId"))
{
requestTelemetry.Properties.Add("correlationId", "id_123456");
}
}
}
2.In Startup.cs -> ConfigureServices method, use the following code:
public void ConfigureServices(IServiceCollection services)
{
//your other code
//register the class MyTelemetryInitializer.
services.AddSingleton<ITelemetryInitializer, MyTelemetryInitializer>();
}
The test result:
If you're using other programming language, please follow the official doc and use the proper method for ITelemetryInitializer.
You don't need to create your own TelemetryInitializer but can just do this from anywhere you can reference the httpContext:
var requestTelemetry = httpContext.Features.Get<RequestTelemetry>();
if (requestTelemetry != null)
{
requestTelemetry.Properties["YourCustomDimension"] = someValue;
}
Properties added in this way will be added to the requests table in Application Insights.
To add for the dependencies and traces tables you can use
System.Diagnostics.Activity.Current.AddBaggage("YourCustomDimension" someValue);
To add to traces when you write a log entry just pass in objects to the LogXXX method with a placeholder in the log message, e.g.
_logger.LogWarning("hello {YourCustomDimension}", someValue);
someValue will be serialized to json so can be a complex object if you like.

It is possible to retrieve host address in application service in abp framework?

It is possible to have host address in app services?
For example we want send email to customer with specific link point to site address. How is this possible?
This came up via Google & the existing answer didn't really help. I don't necessarily agree that app services are the wrong domain for this; in ABP for example, a service is closely connected to a controller and services usually only exist in order to service web requests there. They often execute code in an authorised state that requires a signed-in user, so the whole thing is happening in the implicit domain context of an HTTP request/response cycle.
Accordingly - https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-2.2#use-httpcontext-from-custom-components
Add services.AddHttpContextAccessor(); to something like your Startup.cs - just after wherever you already call services.AddMvc().
Use dependency injection to get hold of an IHttpContextAccessor in your service - see below.
Using constructor-based dependency injection, we add a private instance variable to store the injected reference to the context accessor and a constructor parameter where the reference is provided. The example below has a constructor with just that one parameter, but in your code you probably already have a few in there - just add another parameter and set _httpContextAccessor inside the constructor along with whatever else you're already doing.
using HttpContext = Microsoft.AspNetCore.Http.HttpContext;
using IHttpContextAccessor = Microsoft.AspNetCore.Http.IHttpContextAccessor;
// ...
public class SomeService : ApplicationService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public SomeService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
}
Now service code can read the HTTP context and from there things like the HTTP request's host and port.
public async Task<string> SomeServiceMethod()
{
HttpContext context = _httpContextAccessor.HttpContext;
string domain = context.Request.Host.Host.ToLowerInvariant();
int? port = context.Request.Host.Port;
// ...
}
HttpContext.Current.Request.Url
can get you all the info on the URL. And can break down the url into its fragments.

How I can make private/protected API Using Web API of ASP.NET?

I want to make API(s) using ASP.NET WEB API which should be private or protected.
Using the API(s) I am planning to make Xamarin application and a MVC Website.
Only the Apps can use the API(s), otherwise if anyone get the API(s) then he/she can retrieve data using the API(s). I don't want so!
How can I do it? I need some suggestion.
You can secure you api with API Key Authentication mechanism. Here is a good tutorial
Starting go inside your global.asax.cs file and add
GlobalConfiguration.Configuration.MessageHandlers.Add(new AuthHandler())
Create a class AuthHandler in your project and make that class interface with DelegatingHandler:
public class AuthHandler: DelegatingHandler
Create two methods within your AuthHandler class called ValidateCredentials and SendAsync. The SendAsync method is overridded.
private bool ValidateCredentials(AuthenticationHeaderValue authVal){}
protected override async Task<HttpResponseMessage> SendAsync(HttpResponseMessage request, CancellationToken cancelTok){}
When a class or method has the Authorize filter applied, the MessageHandler in your global.asax is called which calls the Auth handler you created, for example:
[Authorize]
public class SomeController : ApiControler{}
So whats left is the actual authentication of the user. You need to get the header value (placed by the client application), decode it and check it against your database or whatever you use.
private bool ValidateCredentials(AuthenticationHeaderValue authVal)
{
try{
string decodedHeader = new Classes.Strings().decode(authVal);
this.user = // some query to check against database goes here
return true;
}
catch{
// some type of error control here
return false
}
}
protected override async Task<HttpResponseMessage> SendAsync(HttpResponseMessage request, CancellationToken cancelTok)
{
if(ValidateCredentials(request.Headers.Authorization))
{
// store user here to use around the api on this request
}
}
So in short HTTP needs to store your authentication header value. Use that value on each request to filter any class or function you require authentication on. Next, I would read up on http headers, specifically the Authentication header value.

Pass User info to WCF Web service with WCF method vs with Soap header

My WCF Webservice provide all data manipulation operations and my ASP .Net Web application present the user interface.
I need to pass user information with many wcf methods from ASP .Net app to WCF app.
Which one in is better approach regarding passing user info from web app to web service?
1) Pass user information with SOAP header?
ASP .Net Application has to maintain the number of instances of WCF Webservice client as the number of user logged in with the web application. Suppose 4000 user are concurrently active, Web app has to maintain the 4000 instances of WCF webserice client.
Is it has any performance issue?
2) Pass user information with each method call as an additional parameter?
Every method has to add this addtional paramter to pas the user info which does not seems a elegant solution.
Please suggest.
regards,
Dharmendra
I believe it's better to pass some kind of user ID in a header of every message you send to your WCF service. It's pretty easy to do, and it's a good way to get info about user + authorize users on service-side if needed. And you don't need 4000 instances of webservice client for this.
You just need to create Behavior with Client Message Inspector on client side(and register it in your config). For example:
public class AuthClientMessageInspector: IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
request.Headers.Add(MessageHeader.CreateHeader("User", "app", "John"));
return null;
}
}
public class ClientBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
foreach (var operation in endpoint.Contract.Operations)
{
operation.Behaviors.Find<DataContractSerializerOperationBehavior>().MaxItemsInObjectGraph = Int32.MaxValue;
}
var inspector = new AuthClientMessageInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
And extract it from your service-side:
var headers = OperationContext.Current.IncomingMessageHeaders;
var identity = headers.GetHeader<string>("User", "app");

ASP.NET - Create custom context object?

How do I create a globally accessible Context object similar to the HttpContext object?
I want to create a custom class library which I want to reference from a website project. In the website project I want to be able to call the following globally:
ClassLibraryName.Context
I cannot create a global property directly in my classlibrary, so how should this be implemented? (I've seen other applications/products use this approach, one of which is Sitecore which has a custom Sitecore.Context object available)
Edit
Might this be a 'valid' solution?
namespace MyLibrary
{
public class Context
{
public static object ContextualObject
{
get;
set;
}
}
}
Yes, this is not hard to implement, if you always run this class in the context of an ASP.NET application, use this approach:
namespace MyLibrary
{
public class Context
{
public static object ContextualObject
{
get
{
var ctx = System.Web.HttpContext.Current.Items[typeof(Context)];
if (ctx == null)
{
ctx = new Context();
System.Web.HttpContext.Current.Items.Add(typeof(Context), ctx);
}
return ctx;
}
set { System.Web.HttpContext.Current.Items[typeof(Context)] = ctx; }
}
}
}
Essentially wrapping the existing HTTP context to provide your own service. This approach also doesn't store the object while the app lives, it only creates it for the current context, and when that response ends, it will die, and be regenerated during the next lifecycle. If that is not OK, store a static reference to context.
I've used this approach similarly in a class library I have at http://nucleo.codeplex.com, it works well.
HTH.
It depends on the lifetime you want the Context object to have. If you want all clients to use the same context, you can go with a singleton implementation.
If you want the context to be unique for each thread or http request you have to use a per request/thread implementation. One way to implement a per http request implementation would be to have a HttpModule create the object at every BeginRequest event and stick it in the HttpContext Items collection.
public static object ContextualObject
{
get { return HttpContext.Current.Items["MyContext"];}
}
You could create an instance of the object on Session_Start in the Global.asax.

Resources