Possible to use Miniprofiler in dotnetcore 2.0 SPA (Aurelia)?

Hi. I was attempting to wire up Miniprofiler into an Aurelia SPA (referring to ServiceStack MiniProfiler and SPAs ) but I have the problem that the Miniprofiler plugin is being included in my project from SS-MyGet is built for .net 4.6 not core 2.0 ?

However, reading your comments here (Miniprofiler with .Net Core) I am not clear on whether you were saying that its actually not currently possible to use Miniprofiler in SS Core ? It seems that the original MiniPoller project (https://miniprofiler.com/dotnet/AspDotNetCore) now includes capabilities for core - is that something that you’ve seen playing nicely in SS land or am I out of luck?

No MiniProfiler is coupled to ASP.NET Request Context which doesn’t work in .NET Core.

Inspired by this article on how to use miniprofiler with angular SPA, I am tempted to try to implement a new ServiceStackMinipollerCore plugin, albeit slightly modified to just track requests and db connections etc.

Looking at the existing miniprofiler CORE logic, I have dropped that into my ss-only REST api (no ASP CORE MVC etc), and can profile calls db calls and get their execution timing etc, using the following as an example:

	using System.Data.Common;
using System.Net;
using api.service.infrastructure;
using api.service.messages.messages;
using db.a360archive.tables.app;
using ServiceStack;
using ServiceStack.OrmLite;
using StackExchange.Profiling;

namespace api.service
{
	public class SomeDemoCustomAutoQueryEndpoint : ApiEndpoint
	{

		public IAutoQueryDb AutoQuery { get; set; }

		public object Any(QueryResources query)
		{

			var mp = MiniProfiler.StartNew("Test");

			using (mp.Step("Level 1"))
			using (var conn = GetProfiledConnection())
			{
				using (mp.Step("Level 2"))
				{
					conn.Select<AttachmentTypeTbl>();
					conn.Select<ResourceTbl>();
					conn.Select<EntityTbl>();
				}
				// Do some more
				conn.Select<PriorityTbl>();
				conn.Select<PortfolioItemTbl>();
				conn.Select<GlobalTbl>();

				using (var wc = new WebClient())
				using (mp.CustomTiming("http", "GET https://google.com"))
				{
					wc.DownloadString("https://google.com");
				}
			}
			mp.Stop();

			// So.. I was thinking about instead of writing the events out as plain text headers, instead 
			// either add a chunk of content to the response message / dtos like the way StackTrace data is added, 
			// or use a response filter to add customer ss-mp-trace-headers... 
			var thisIsCurrentlyAPlainTextPrintoutOfWhatHappened = MiniProfiler.Current.RenderPlainText();

			return AutoQuery.Execute(query, AutoQuery.CreateQuery(query, Request));
		}


		/// <summary>
		/// Returns an open connection that will have its queries profiled.
		/// </summary>
		/// <returns>the database connection abstraction</returns>
		public DbConnection GetProfiledConnection()
		{
			//DbConnection cnn = new Microsoft.Data.Sqlite.SqliteConnection("Data Source=:memory:");

			// to get profiling times, we have to wrap whatever connection we're using in a ProfiledDbConnection
			// when MiniProfiler.Current is null, this connection will not record any database timings
			if (MiniProfiler.Current != null)
			{
				var dbConn = Db.ToDbConnection() as DbConnection;
				var profiledDb = new StackExchange.Profiling.Data.ProfiledDbConnection(dbConn, MiniProfiler.Current);
				return profiledDb;
			}

			return (DbConnection)Db;
			//cnn.Open();
			//return cnn;
		}
	}
}

Which successfully tracks my sql queries and stores them in that local variable, in a plain text string. The question is, how to best return those to the consuming client / SPA UI. As per my comment in that code, I wonder what you think would be “best” and/or most likely to be able to be used by the broader SS community? Would it be better to try to push MiniP content (eg the Timing class elements as dtos) into the ResponseMessage dto (ala the ResponseStatus/Stacktrace data) OR… stick to a more original MP style and return custom headers, which could be serialized TimingDto JSON and intercepted and rendered by SPA middleware like the Angular example?

In particular, I refer to your notes on: how to customize responses

Current issues/options I can see:

  • the Service.Db property is read only, so its not easy to wrap in the profiled connection - instead, i have to wrap it manually and remember to use a profiled connection within each endpoint, as in the example above
  • I could try to, based on the request messages implementing some new interface IHasMiniProfDbConn (for example) try to do some trickery like make all my logic us an OptionallyProfiledDb { get { if (request is IHasminiProfdbconn && Miniprofiler.Current != null) return wrappeddb; otherwise, return Db } … then use of the profiler would be more manual and require each endpoint to be explicitly coded to be traceable rather than global opt in.
  • The results would not extend to the full pipeline to show entire request times… and I’m not sure how to make this more broadly suitable to wire into the full request-response pipeline to capture and profile the full end-to-end server API operations.

Thoughts?

It’s not clear what all the questions are, but I’ll try my best to answer them:

Try to reuse as much of ASP.NET Core MiniProfiler

My initial approach would be to see you’re just able to reuse the ASP.NET Core MiniProfiler. .NET Core lets you register loosely-coupled handlers to the same pipeline, so it should be able to work with ServiceStack ASP.NET Core Apps given, it’s itself just another handler.

MiniProfiler’s dependencies is configured in .NET Core with:

https://github.com/MiniProfiler/dotnet/blob/master/samples/Samples.AspNetCore/Startup.cs#L41

and registered to .NET Core’s pipeline with:

app.UseMiniProfiler();

Then it looks like it’s rendered in Razor Views with a tag helper:

https://github.com/MiniProfiler/dotnet/blob/master/samples/Samples.AspNetCore/Views/Shared/_Layout.cshtml#L50

For static .html in SPA’s you’d need to replace it with a <script /> include, I’d ask them what approach they recommend.

The API for custom profiling steps is also similar, e.g:

using (MiniProfiler.Current.Step("Example Step"))
{
}

Alternative is to wrap inside a Plugin

Otherwise the ideal API would be similar to the existing MiniProfilerFeature by wrapping the functionality in a plugin that can be registered like:

Plugins.Add(new MiniProfilerFeature()); 

And have some snippet they could add to their pages, e.g. how MiniProfiler is included in Razor views:

@ServiceStack.MiniProfiler.Profiler.RenderIncludes().AsRaw() 

For a SPA app it would ideally just be a <script /> tag to enable the functionality, similar to how Hot Reloading works.

RDBMS profiling

You should be able to profile DB connections the same way that SQL profiling is enabled in MiniProfiler, e.g:

this.Container.Register<IDbConnectionFactory>(c =>
    new OrmLiteConnectionFactory(
        "~/App_Data/db.sqlite".MapHostAbsolutePath(), SqliteDialect.Provider) {
            ConnectionFilter = x => new ProfiledDbConnection(x, Profiler.Current)
    });

Alternatively the Db property calls GetDbConnection() in your AppHost which you can override to return a profiled connection.

Yeah, sorry about the confusion. It’s been a long day. I should have mentioned, I am currently developing a pure api backend, without Razor or MVC etc, our front end is pure Angular SPA. When I read the existing MiniP.Core doco, I read it as requiring MVC to function, so my first hacky experiments were using the (non MVC) console-only nuget and wondering how I might wire that into the framework.

Maybe its easiest to include Asp.NET Core into my backend anyway, to get this feature working, but I didnt want to bring all thsoe libraries in just for debugging purposes.

I’ll take a look when I next get some time and let you know if I have success. Thanks.

FYI:

It’s not always pretty, but I got this working, and maybe it will help someone else in future:

http://inayearorso.io/2018/04/18/servicestack-miniprofiler-dotnetcore-angular2/

1 Like

Sweet nice write up, thx!

No worries, was fun and very valuable to me to get it working. One thing I would ask though, is whats the recommended/best way to access servicestack auth logic from within that services.AddMiniprofilerOptions section?

// (Optional) To control authorization, you can use the Func<HttpRequest, bool> options:
// (default is everyone can access profilers)
options.ResultsAuthorize = request => MyGetUserFunction(request).CanSeeMiniProfiler;
options.ResultsListAuthorize = request => MyGetUserFunction(request).CanSeeMiniProfiler;

// (Optional) Profiles are stored under a user ID, function to get it:
// (default is null, since above methods don't use it by default)
options.UserIdProvider =  request => MyGetUserIdFunction(request);

I would like to restrict the miniprofiler from anyone who isn’t of a particular role, and also probably attach the results to a particular session to limit noise. But if the (request) is an MVC request, and not routed through SS, I am unsure how to attach auth logic/check session etc to it?

I have taken a look through the existing SS.Miniprofiler code, but I don’t understand it enough to know whether there is something in there that I can use, I don’t see any example of attaching auth checks to MP.

I lol’d at this comment though:

// put our routes at the beginning, like a boss
routes.Insert(0, route);

Basically you would just need to get the ss-id Cookie which you can use to resolve the UserSession from the registered ICacheClient, basically something like:

var sessionId = request.Cookies[Keywords.SessionId];
if (!string.IsNullOrEmpty(sessionId))
{
    var sessionKey = SessionFeature.GetSessionKey(sessionId);
    var cache = HostContext.TryResolve<ICacheClient>();
    var userSesion = cache.Get<IAuthSession>(sessionKey);
    if (userSesion.IsAuthenticated)
    {
        //...
    }
}
1 Like

For anyone else who arrives here, the auth approach works perfectly, and my original blog post has been updated accordingly., with code that is basically:

// (Optional) To control authorization, you can use the Func<HttpRequest, bool> options:
// (default is everyone can access profilers)
options.ResultsAuthorize = IsUserAllowedToSeeMiniProfilerUI;
options.ResultsListAuthorize = IsUserAllowedToSeeMiniProfilerUI;

and

    /// <summary>
/// Always prevent non-admin users from seeing the Miniprofiler results.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private bool IsUserAllowedToSeeMiniProfilerUI(HttpRequest request)
{
	var sessionId = request.Cookies[Keywords.SessionId];
	if (string.IsNullOrEmpty(sessionId)) return false;
	var sessionKey = SessionFeature.GetSessionKey(sessionId);
	var cache = HostContext.TryResolve<ICacheClient>();
	var userSession = cache.Get<IAuthSession>(sessionKey);
	if (userSession == null || !userSession.IsAuthenticated) return false;
	if (userSession.Roles.Contains(SystemRolesEnum.ADMIN.ToString())) return true;
	if (userSession.Roles.Contains(SystemRolesEnum.SYSADMIN.ToString())) return true;
	return false;
}
1 Like