ASP.NET Core Application Insights - Track Authenticated User ID - asp.net-core

I have read everything I could find, and everything suggests using ITelemetryInitializer. However, the best I can figure - that only runs once as its a Singleton. It also runs before the user has authenticated, so I don't have the data I need quite yet.
I have added the logic to my client side tracking as that was pretty straightforward:
#if (_userService != null && _userService.IsAuthenticated())
{
<script>
appInsights.setAuthenticatedUserContext('#_userService.GetCurrentUserId()');
</script>
}
Note in this case _userService is a DI service I use to access the currently authenticated user (which for now just uses ClaimsPrincipal).
What I need to do now is add the same setting to the server side telemetry, but I can't seem to figure out how to tie-in to it. Anyone out there figured this out?

So I guess this falls into the bucket of "as soon as you as StackOverflow you figure it out yourself".
I don't know what I did wrong, but I do know once I changed my initializer from implementing ITelemetryInitializer to implementing TelemetryInitializerBase, it started working.

Related

Make Strawberry Shake request from Blazor with or without credentials

In my Blazor app (which uses Azure B2C), I want to be able to call an endpoint whether the user is authenticated or not.
I've searched quite a bit, and everything I find says I should create two HttpClients (example), one for anonymous and one for authenticated, or use IHttpClientFactory with named clients.
The problem is I am using Strawberry Shake which only allows me to configure HttpClient once (it is using a named client and IHttpClientFactory internally).
Their documentation gives a simple example of setting authentication:
services
.AddConferenceClient()
.ConfigureHttpClient((serviceProvider, client) =>
{
var token = serviceProvider.GetRequiredService<ISomeService>().Token;
});
I thought I could use this to conditionally select which handler(s) I wanted, but the only ways I can find to get the token (IAccessTokenProvider.RequestAccessToken()) or validate authentication (Task<AuthenticationState>) require async calls, which are not allowed in this context. Even .Result doesn't work (not that I wanted to use it anyway).
My last thought is that maybe I could accomplish this by inheriting from BaseAddressAuthorizationMessageHandler or chaining handlers, but I can't figure out how. I even tried copying the source code and modifying it, but still couldn't get it to work (UPDATE: Actually, that did work, but it still seems less than ideal).
So many approaches seem workable, but ultimately fail me. How can I get this to work? Please provide code example if possible.

Serilog using EnrichDiagnosticContext with additional properties not being logged in SignalR Hub

I have recently implemented Serilog logging into my ASP.NET Core/.NET5 web app that uses SignalR. I'm using the Elasticsearch sink and everything is largely working as expected. I decided to add some additional HttpContext properties to be logged on each request, so I went down the road of extending the call to UseSerilogRequestLogging() in StartUp.cs as to enrich the diagnostic context with a couple of extra properties (mainly because this seemed like the simplest way to do it):
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("HttpRequestClientIP", httpContext.Connection.RemoteIpAddress);
diagnosticContext.Set("UserName", httpContext.User?.Identity?.Name == null ? "(anonymous)" : httpContext.User.Identity.Name);
};
});
At first, this seemed to work as expected until I noticed it wasn't always working. I really want the extra properties logged on all log records written, and it seems to work fine on log records that are written automatically by Serilog when typical HTTP GETs, HTTP POSTs, etc. occur... But in my Signalr Hub class, I have a couple of places where I'm manually writing my own log records like Logger.Log(LogLevel.Information, "whatever.."), but these extra properties are simply not there on these records.
What am I missing here? Is it something about this being in a Signalr Hub that makes them unavailable? Or perhaps there's something I'm doing wrong with my Logger.Log() calls?
Any ideas would be appreciated.
Thanks-
It's not gonna to work with signalR.
Behind the screen, app.UseSerilogRequestLogging make use of a middleware in the request pipeline, that call RequestLoggingMiddleware as what you can see in detail here.
SignalR use the first Http request to setting to connection up to websocket, which won't goes through the pipeline at all. Therefore, doesn't have anything to do with RequestLoggingMiddleware, which you are using to logging out the request.
I finally ended up going with a couple of custom Enrichers. I did experiment briefly with middleware vs enrichers and they both seem to work as expected. Both always added the additional properties to all log entries. I'm still not quite sure I understand why the DiagnosticContext option behaves the way it does, unless it is simply due to the logging in question being in a SignalR hub as #Gordon Khanh Ng. posted. If that were the root of the problem though, you wouldn't think the enrichers or middleware would work either.

Redis-backed session state not saving everything

We are trying to move from server session (IIS) to Redis-backed session. I updated my web.config with the custom sessionState configuration. I'm finding that only SOME of my key/value pairs are being saved. Of the 5 I expect to be in there, there are only 2. I verified all my code is ultimately hitting HttpContext.Current.Session.Add. I verified that my POCOs are marked as serializable. Looking at monitoring, I see that it adds the first two pairs, then everything after that just doesn't make it. No hit, no rejection, no exceptions. Nothing.
Anyone ever see this? Know where I could start to look to resolve?
TIA,
Matt
Update 1: I've switched to using a JSON serializer to store my data. Same thing. Doesn't seem to be a serialization issue.
Update 2: I've now downloaded the source code, compiled and am debugging it. The method SetAndReleaseItemExclusive, which seems to send the session items to Redis, is only hit once, though it should be hit more than once as my web site handle SSO and bounces from page to page to load the user and such. Have to investigate why it's only firing once...
Figured it out. Turns out that my AJAX request to an "API" endpoint without my MVC app did not have the appropriate session state attached. Therefore, the SetAndReleaseItemExclusive was never called. Adding this fixed it:
protected void Application_PostAuthorizeRequest()
{
if (HttpContext.Current.Request.Url.LocalPath == "/api/user/load")
HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
else
HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.ReadOnly);
}

Why is Mage_Persistent breaking /api/wsdl?soap

I get the following error within Magento CE 1.6.1.0
Warning: session_start() [<a href='function.session-start'>function.session-start</a>]: Cannot send session cookie - headers already sent by (output started at /home/dev/env/var/www/user/dev/wdcastaging/lib/Zend/Controller/Response/Abstract.php:586) in /home/dev/env/var/www/user/dev/wdcastaging/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php on line 119
when accessing /api/soap/?wsdl
Apparently, a session_start() is being attempted after the entire contents of the WSDL file have already been output, resulting in the error.
Why is magento attempting to start a session after outputting all the datums? I'm glad you asked. So it looks like controller_front_send_response_after is being hooked by Mage_Persistent in order to call synchronizePersistentInfo(), which in turn ends up getting that session_start() to fire.
The interesting thing is that this wasn't always happening, initially the WSDL loaded just fine for me, initially I racked my brains to try and see what customization may have been made to our install to cause this, but the tracing I've done seems to indicate that this is all happening entirely inside of core.
We have also experienced a tiny bit of (completely unrelated) strangeness with Mage_Persistent which makes me a little more willing to throw my hands up at this point and SO it.
I've done a bit of searching on SO and have found some questions related to the whole "headers already sent" thing in general, but not this specific case.
Any thoughts?
Oh, and the temporary workaround I have in place is simply disabling Mage_Persistent via the persistent/options/enable config data. I also did a little bit of digging as to whether it might be possible to observe an event in order to disable this module only for the WSDL controller (since that seems to be the only one having problems), but it looks like that module relies exclusively on this config flag to determine it's enabled status.
UPDATE: Bug has been reported: http://www.magentocommerce.com/bug-tracking/issue?issue=13370
I'd report this is a bug to the Magento team. The Magento API controllers all route through standard Magento action controller objects, and all these objects inherit from the Mage_Api_Controller_Action class. This class has a preDispatch method
class Mage_Api_Controller_Action extends Mage_Core_Controller_Front_Action
{
public function preDispatch()
{
$this->getLayout()->setArea('adminhtml');
Mage::app()->setCurrentStore('admin');
$this->setFlag('', self::FLAG_NO_START_SESSION, 1); // Do not start standart session
parent::preDispatch();
return $this;
}
//...
}
which includes setting a flag to ensure normal session handling doesn't start for API methods.
$this->setFlag('', self::FLAG_NO_START_SESSION, 1);
So, it sounds like there's code in synchronizePersistentInf that assumes the existence of a session object, and when it uses it the session is initialized, resulting in the error you've seen. Normally, this isn't a problem as every other controller has initialized a session at this point, but the API controllers explicitly turns it off.
As far as fixes go, your best bet (and probably the quick answer you'll get from Magento support) will be to disable the persistant cart feature for the default configuration setting, but then enable it for specific stores that need it. This will let carts
Coming up with a fix on your own is going to be uncharted territory, and I can't think of a way to do it that isn't terribly hacky/unstable. The most straight forward way would be a class rewrite on the synchronizePersistentInf that calls it's parent method unless you've detected this is an API request.
This answer is not meant to replace the existing answer. But I wanted to drop some code in here in case someone runs into this issue, and comments don't really allow for code formatting.
I went with a simple local code pool override of Mage_Persistent_Model_Observer_Session to exit out of the function for any URL routes that are within /api/*
Not expecting this fix to need to be very long-lived or upgrade-friendly, b/c I'm expecting them to fix this in the next release or so.
public function synchronizePersistentInfo(Varien_Event_Observer $observer)
{
...
if ($request->getRouteName() == 'api') {
return;
}
...
}

Entity Framework: AttachAsModified failure / confusion :)

Ok... I tried google and didn't get many hits. I dont want to abuse So but this is one of the best places to ask and EF isn't well documented.
My fails because the GetOriginal() returns null in UpdateCmsProductCategory. I assume that means currentCmsProductCategory is not in the ChangeSet. Ok... how do I put it in the changeset?
Here is the sequence...
I pull a CmsProductCategory down over Wcf.
I make changes.
I call the Wcf update method...
public void UpdateProductCategory(CmsProductCategory category)
{
domainservice.UpdateCmsProductCategory(category);
}
Which calls the Domain servide method...
public virtual void UpdateCmsProductCategory(CmsProductCategory currentCmsProductCategory)
{
this.Context.AttachAsModified(currentCmsProductCategory,
this.ChangeSet.GetOriginal(currentCmsProductCategory));
}
And that should work - but no, it Exceptions on me when GetOriginal() fails. I feel like I am missing a step between when the code modifies it and I pass it to Wcf.
Any hints / pointers to good documentation?
Thanks!
Your problem is probably that you lose the "context".
When you make the call to update the "this.Context" is not the same as the one you read it from.
WCF has a concept of "per call" and "per session". The "per call" is default your are therefore getting a new instance of the domain service. You may be able to solve it using per session.
Have a look at this link: http://msdn.microsoft.com/en-us/magazine/cc163590.aspx
Also try writing a test to check that what you are doing works without transfering the data over wcf.