Adding custom properties for each request in Application Insights metrics - c#

I d'like to add custom properties to metrics taken by Application Insights to each request of my app. For example, I want to add the user login and the tenant code, such as I can segment/group the metrics in the Azure portal.
The relevant doc page seems to be this one : Set default property values
But the example is for an event (i.e. gameTelemetry.TrackEvent("WinGame");), not for an HTTP request :
var context = new TelemetryContext();
context.Properties["Game"] = currentGame.Name;
var gameTelemetry = new TelemetryClient(context);
gameTelemetry.TrackEvent("WinGame");
My questions :
What is the relevant code for a request, as I have no specific code at this time (it seems to be automatically managed by the App Insights SDK) : Is just creating a TelemetryContext sufficient ? Should I create also a TelemetryClient and if so, should I link it to the current request ? How ?
Where should I put this code ? Is it ok in the Application_BeginRequest method of global.asax ?

It looks like adding new properties to existing request is possible using ITelemetryInitializer as mentioned here.
I created sample class as given below and added new property called "LoggedInUser" to request telemetry.
public class CustomTelemetry : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null) return;
requestTelemetry.Properties.Add("LoggedInUserName", "DummyUser");
}
}
Register this class at application start event.
Example below is carved out of sample MVC application I created
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
TelemetryConfiguration.Active.TelemetryInitializers
.Add(new CustomTelemetry());
}
}
Now you can see custom property "LoggedInUserName" is displayed under Custom group of request properties. (please refer screen grab below)
Appinsight with custom property

Related to the first question "how to add custom event to my request / what is the relevant code to a request", I think the main confusion here is related to the naming.
The first thing that we need to point out is that there are different kinds of information that we can capture with Application Insights:
Custom Event
Request
Exception
Trace
Page View
Dependency
Once we know this, we can say that TrackEvent is related to "Custom Events", as TrackRequest is related to Requests.
When we want to save a request, what we need to do is the following:
var request = new RequestTelemetry();
var client = new TelemetryClient();
request.Name = "My Request";
client.TrackRequest(request);
So let's imagine that your user login and tenant code both are strings. We could make a new request just to log this information using the following code:
public void LogUserNameAndTenant(string userName, string tenantCode)
{
var request = new RequestTelemetry();
request.Name = "My Request";
request.Context.Properties["User Name"] = userName;
request.Context.Properties["Tenant Code"] = tenantCode;
var client = new TelemetryClient();
client.TrackRequest(request);
}
Doing just a TelemetryContext will not be enough, because we need a way to send the information, and that's where the TelemetryClient gets in place.
I hope it helps.

You can use the static HttpContext.Current's Items dictionary as a short term (near stateless) storage space to deliver your custom property values into the default telemetry handler with a custom ITelemetryInitializer
Implement handler
class AppInsightCustomProps : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
// Is this a TrackRequest() ?
if (requestTelemetry == null) return;
var httpCtx = HttpContext.Current;
if (httpCtx != null)
{
var customPropVal = (string)httpCtx.Items["PerRequestMyCustomProp"];
if (!string.IsNullOrWhiteSpace(customPropVal))
{
requestTelemetry.Properties["MyCustomProp"] = customPropVal;
}
}
}
}
Hook it in. Put this inside Application_Start in global.asax.cs
TelemetryConfiguration.Active.TelemetryInitializers.Add(new AppInsightCustomProps());
Program the desired custom property, anywhere in your request pipeline have something like
if (HttpContext.Current != null)
{
HttpContext.Current.Items["PerRequestMyCustomProp"] = myCustomPropValue;
}

As mentioned by Alan, you could implement the IContextInitializer interface to add custom properties to ALL telemetry sent by Application Insights. However, I would also suggest looking into the ITelemtryInitializer interface. It is very similar to the context initializer, but it is called for every piece of telemetry sent rather than only at the creation of a telemetry client. This seems more useful to me for logging property values that might change over the lifetime of your app such as user and tenant related info like you had mentioned.
I hope that helps you out. Here is a blog post with an example of using the ITelemetryInitializer.

In that doc, scroll down a few lines to where it talks about creating an implementation of IContextInitializer. You can call that in any method that will be called before telemetry starts rolling.
Your custom properties will be added to all events, exceptions, metrics, requests, everything.

Related

How to filter UrlReferrer from a whitelist in ASP.NET?

I need to create a single page that is going to be a part of an existing ASP.NET MVC website that is only open or accessible if came from allowed websites (Referrer). Our partners are planning
to load this page using an iFrame. This page allows any user to quickly enter the details and save into
our database.
example: www.mysecuredwebsite.com/publicpage
Since this is going to be public single page only and no logins, the only security is
only allow if the referrer is in the list of allowed sites.
Example:
www.abc.com
www.randomwebsite.com
www.amazingsite.com
Any request from any other sites will be redirected to an error page.
What is the best approach to this problem? I'm thinking of creating a custom attribute that will be used
to decorate the controller which then reads
a list of allowed sites from the app.config orany type of config.
Or maybe from the SQLDB since the site is using SQLDB? The list I think will be frequently change
depending on the growing number of clients. It should be easily configurable and does not require
redeployment.
Any thoughts? Thanks!
You can use two methods
Method 1: Use ActionFilterAttribute.
Method 2: Use the Application_BeginRequest method in the Global.asax file.
Note: It is better to put the allowed urls in the database so that it can be easily changed.
I will explain the first method
The following filter checks whether UrlReferrer is allowed. If it
is not allowed, it will be referred to the error page.
add UrlReferrerFilterAttribute
using System.Web.Mvc;
using System.Web.Routing;
namespace yournamespace
{
public class UrlReferrerFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string urlReferrer = string.Empty;
if (filterContext.HttpContext.Request.UrlReferrer != null)
{
urlReferrer = filterContext.HttpContext.Request.UrlReferrer.AbsoluteUri;
if(!db.UrlReferrerTable.Any(a => a.Url == urlReferrer))
{
var values = new RouteValueDictionary(new
{
action = "ErrorPage",
controller = "Home"
});
filterContext.Result = new RedirectToRouteResult(values);
}
}
base.OnActionExecuting(filterContext);
}
}
}
now use
[UrlReferrerFilter]
public ActionResult YourAction()
{
//..................
//..................
}

C# WCF Service Get Status Code

I have a very basic WCF service which has a method named SaveSchoolName(string SchoolName) which basically returns as boolean value of True if the operation is good.
.
I added a service reference to my client application and is consuming the service as follows:
MyService.WebServicesClient svc = new MyService.WebServicesClient();
bool dataSaved = false;
dataSaved = svc.SaveSchoolName("School Name");
if(dataSaved){
// do something.
}
else{
// log not saved.
}
I want to know how do I determine the Http Status Code (200 - OK) for the WCF Service call. I have tried to search but none seems to provide any detailed info on how I would be able to get the response headers from invoking the method.
You need to create a client message inspector for this.
Check the below code out ... to make it work just add the inspector to your client. BTW, obviously this only works for HTTP :)
public class HttpStatusCodeMessageInspector : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
if (reply.Properties.ContainsKey(HttpResponseMessageProperty.Name))
{
var httpResponseProperty = (HttpResponseMessageProperty)reply.Properties[HttpResponseMessageProperty.Name];
Console.WriteLine($"Response status is {(int)httpResponseProperty.StatusCode}");
}
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
return null;
}
}
To extend #Jorge's answer, I started by implementing the IClientMessageInspector and IEndpointBehavior from the example here. I replaced the contents of IClientMessageInspector.AfterReceiveReply with the code provided by Jorge:
public void AfterReceiveReply(ref Message reply, object correlationState)
{
if (reply.Properties.ContainsKey(HttpResponseMessageProperty.Name))
{
var httpResponseProperty = (HttpResponseMessageProperty)reply.Properties[HttpResponseMessageProperty.Name];
Console.WriteLine($"Response status is {(int)httpResponseProperty.StatusCode}");
}
}
Now, you can choose to continue inheriting BehaviorExtensionElement and add the behavior to your client configuration file. If you take this approach you'll be done.
However, since my client configuration was created programmatically, I needed a few additional steps here.
In the docs you can see that an Endpoint Behavior can be added by using the ServiceEndpoint.Behaviors.Add method. But how do we get access to the ServiceEndpoint object?
This can be done by first creating your service client object, and then using its Endpoint property like so:
''' VB.NET
'' Creating the binding obj
Dim objMyBinding As New System.ServiceModel.WSHttpBinding()
objMyBinding.Name = "YOUR_BINDING_NAME"
'' Other details of binding added here ''
'' Creating the endpoint address obj
Dim objMyEndpoint As System.ServiceModel.EndpointAddress
objMyEndpoint = New System.ServiceModel.EndpointAddress(New Uri(ENDPOINT_ADDR_HERE))
'' Creating the Service obj
Dim objWebServices As CalculatorService = New CalculatorService(objMyBinding, objMyEndpoint)
'' Finally adding the behavior to the Service Endpoint (CustomEndpointBehavior is implementation of IEndpointBehavior)
objWebServices.Endpoint.EndpointBehaviors.Add(New CustomEndpointBehavior)
/// C#.NET
// Creating the binding obj
System.ServiceModel.WSHttpBinding objMyBinding = New System.ServiceModel.WSHttpBinding();
objMyBinding.Name = "YOUR_BINDING_NAME";
// Other details of binding added here //
// Creating the endpoint address obj
System.ServiceModel.EndpointAddress objMyEndpoint;
objMyEndpoint = New System.ServiceModel.EndpointAddress(New Uri(ENDPOINT_ADDR_HERE));
// Creating the Service obj
CalculatorService objWebServices = New CalculatorService(objMyBinding, objMyEndpoint);
// Finally adding the behavior to the Service Endpoint (CustomEndpointBehavior is implementation of IEndpointBehavior)
objWebServices.Endpoint.EndpointBehaviors.Add(New CustomEndpointBehavior());

Per module authentication with returnUrl

I need to have Forms Authentication for some modules and Stateless Authentication for others. With Forms Authentication, the login page needs to set a returnUrl.
We had this working with application-wide Forms Authentication (enabled for pipelines), but we're stuck trying to enable hybrid authentication.
Per https://github.com/NancyFx/Nancy/issues/2439 we have to enable the forms authentication in the module constructor:
public abstract class FormsAuthenticationModuleBase : NancyModule
{
protected FormsAuthenticationModuleBase(IUserMapper userMapper)
: this(userMapper, string.Empty)
{
}
protected FormsAuthenticationModuleBase(IUserMapper userMapper, string modulePath)
: base(modulePath)
{
var formsAuthConfiguration = new FormsAuthenticationConfiguration
{
RequiresSSL = false,
UserMapper = userMapper,
RedirectUrl = "/login?returnUrl=" + Context.Request.Headers["X-Original-URL"].FirstOrDefault()
};
// Enable calls RequiresAuthentication.
FormsAuthentication.Enable(this, formsAuthConfiguration);
}
}
The issue is that Context is null at construction time.
How can we enable Forms Authentication on a per module basis and set a RedirectUrl that includes a returnUrl?
(See also https://github.com/NancyFx/Nancy/wiki/Forms-authentication )
Update
On the advice of #khellang I tried adding a before hook:
Before.AddItemToStartOfPipeline(
ctx =>
{
if (!formsAuthConfiguration.RedirectUrl.Contains("?"))
{
formsAuthConfiguration.RedirectUrl +=
"?returnUrl=" + ctx.Request.Headers["X-Original-URL"].FirstOrDefault();
}
return null;
});
(With the original RedirectUrl = "/login")
This results in a 405 that I can't debug into. (Same for the Before += syntax.)
He also suggested moving everything into the hook, rather than storing the configuration and mutating it; that didn't work either.
Currently I'm using if/else logic in RequestStartup (as opposed to the above approach). That works.
Using if/else logic in RequestStartup works.

Operation Handler WCF Web Api malfunctionging?

I'm building a web api using WCF web api preview 6, currently I'm stuck with a little problem. I would like to have an operation handler to inject an IPrincipal to the operation in order to determine which user is making the request. I already have that Operation Handler and is already configured. But I noticed that when I decorate the operation with the WebInvoke attribute and simultaneously the operation receives an IPrincipal and other domain object, the system throws an exception telling me:
The HttpOperationHandlerFactory is unable to determine the input parameter that should be associated with the request message content for service operation 'NameOfTheOperation'. If the operation does not expect content in the request message use the HTTP GET method with the operation. Otherwise, ensure that one input parameter either has it's IsContentParameter property set to 'True' or is a type that is assignable to one of the following: HttpContent, ObjectContent1, HttpRequestMessage or HttpRequestMessage1.
I do not know what is happening here. To give you some background I'll post some of my code to let you know how am I doing things.
The operation:
[WebInvoke(UriTemplate = "", Method = "POST")]
[Authorization(Roles = "")]
public HttpResponseMessage<dto.Diagnostic> RegisterDiagnostic(dto.Diagnostic diagnostic, IPrincipal principal)
{
......
}
WCF web api knows when to inject the IPrincipal because I decorate the operation with a custom Authorization attribute.
The configuration in the Global file:
var config = new WebApiConfiguration() {EnableTestClient = true};
config.RegisterOAuthHanlder(); //this is an extension method
routes.SetDefaultHttpConfiguration(config);
routes.MapServiceRoute<MeasurementResource>("Measurement");
routes.MapServiceRoute<DiagnosticResource>("Diagnostic");
Then the RegisterOAuthHandler method adds an operation handler to the operation if it's been decorated with the custom authorization attibute. this is how it looks:
public static WebApiConfiguration RegisterOAuthHanlder(this WebApiConfiguration conf)
{
conf.AddRequestHandlers((coll, ep, desc) =>
{
var authorizeAttribute = desc.Attributes.OfType<AuthorizationAttribute>().FirstOrDefault();
if (authorizeAttribute != null)
{
coll.Add(new OAuthOperationHandler(authorizeAttribute));
}
});
return conf;
}
public static WebApiConfiguration AddRequestHandlers(
this WebApiConfiguration conf,
Action<Collection<HttpOperationHandler>, ServiceEndpoint, HttpOperationDescription> requestHandlerDelegate)
{
var old = conf.RequestHandlers;
conf.RequestHandlers = old == null ? requestHandlerDelegate : (coll, ep, desc) =>
{
old(coll, ep, desc);
};
return conf;
}
Can somebody help me with this? Thank you in advanced!
Try wrapping your Diagnostic param in ObjectContent i.e. ObjectContent<Diagnostic>. Then you will use the ReadAs() method to pull out the object.
It should work.

Redirect users with suspended accounts without creating redirect loop

I have a subscription based MVC 2 application with the basic .NET Membership service in place (underneath some custom components to manage the account/subscription, etc). Users whose accounts have lapsed, or who have manually suspended their accounts, need to be able to get to a single view in the system that manages the status of their account. The controller driving that view is protected using the [Authorize] attribute.
I want to ensure that no other views in the system can be accessed until the user has re-activated their account. In my base controller (from which all my protected controllers derive) I tried modifying the OnActionExecuting method to intercept the action, check for a suspended account, and if it's suspended, redirect to the single view that manages the account status. But this puts me in an infinite loop. When the new action is hit, OnActionExecuting gets called again, and the cycle keeps going.
I don't really want to extend the [Authorize] attribute, but can if need be.
Any other thoughts on how to do this at the controller level?
EDIT: in the base controller, I was managing the redirect (that subsequently created the redirect loop) by modifying the filterContext.Result property, setting it to the RedirectToAction result of my view in question. I noticed everytime the loop occurs, filterContext.Result == null. Perhaps I should be checking against a different part of filterContext?
Ok, so here's my solution in case it helps anyone else. There's got to be a more elegant way to do this, and I'm all ears if anyone has a better idea.
In my BaseController.cs:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
ViewData["CurrentUser"] = CurrentUser; // this is a public property in the BaseController
if (CurrentUser != null && CurrentUser.Account.Status != AccountStatus.Active)
{
// if the account is disabled and they are authenticated, we need to allow them
// to get to the account settings screen where they can re-activate, as well as the logoff
// action. Everything else should be disabled.
string[] actionWhiteList = new string[] {
Url.Action("Edit", "AccountSettings", new { id = CurrentUser.Account.Id, section = "billing" }),
Url.Action("Logoff", "Account")
};
var allowAccess = false;
foreach (string url in actionWhiteList)
{
// compare each of the whitelisted paths to the raw url from the Request context.
if (url == filterContext.HttpContext.Request.RawUrl)
{
allowAccess = true;
break;
}
}
if (!allowAccess)
{
filterContext.Result = RedirectToAction("Edit", "AccountSettings", new { id = CurrentUser.Account.Id, section = "billing" });
}
}
base.OnActionExecuting(filterContext);
}

Categories