File: RazorBrokeredServiceBase.FactoryBase`1.cs
Web Access
Project: src\src\Razor\src\Razor\src\Microsoft.CodeAnalysis.Remote.Razor\Microsoft.CodeAnalysis.Remote.Razor.csproj (Microsoft.CodeAnalysis.Remote.Razor)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Remote.Razor.Logging;
using Microsoft.ServiceHub.Framework;
using Microsoft.ServiceHub.Framework.Services;
using Nerdbank.Streams;
 
namespace Microsoft.CodeAnalysis.Remote.Razor;
 
internal abstract partial class RazorBrokeredServiceBase
{
    /// <remarks>
    /// Implementors of <see cref="IServiceHubServiceFactory" /> (and thus this class) MUST provide a parameterless constructor
    /// or ServiceHub will fail to construct them.
    /// </remarks>
    internal abstract class FactoryBase<TService> : IServiceHubServiceFactory, IInProcServiceFactory
        where TService : class
    {
        protected abstract TService CreateService(in ServiceArgs args);
 
        public Task<object> CreateAsync(
            Stream stream,
            IServiceProvider hostProvidedServices,
            ServiceActivationOptions serviceActivationOptions,
            IServiceBroker serviceBroker,
            AuthorizationServiceClient? authorizationServiceClient)
        {
            // Dispose the AuthorizationServiceClient since we won't be using it
            authorizationServiceClient?.Dispose();
 
            return CreateAsync(stream, hostProvidedServices, serviceBroker);
        }
 
        public Task<object> CreateInProcAsync(IServiceProvider hostProvidedServices)
            => CreateInternalAsync(stream: null, hostProvidedServices, serviceBroker: null);
 
        private Task<object> CreateAsync(Stream stream, IServiceProvider hostProvidedServices, IServiceBroker serviceBroker)
        {
#if NET
            // So that we can control assembly loading, we re-load ourselves in the shared Razor ALC and perform the creation there.
            // That ensures that the service type we return is in the Razor ALC and any dependencies it needs will be handled by the
            // Razor ALC dependency loading rather than the default ServiceHub ALC that we're in right now.
            var assemblyInRazorAlc = RazorAssemblyLoadContext.Instance.LoadFromAssemblyName(GetType().Assembly.GetName());
            var thisInRazorAlc = assemblyInRazorAlc.CreateInstance(GetType().FullName!)!;
 
            var createInternalAsyncFunc = thisInRazorAlc.GetType().GetMethod(nameof(CreateInternalAsync), System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!;
            var result = (Task<object>)createInternalAsyncFunc.Invoke(thisInRazorAlc, [stream, hostProvidedServices, serviceBroker])!;
            return result;
#else
            return CreateInternalAsync(stream, hostProvidedServices, serviceBroker);
#endif
        }
 
        protected virtual async Task<object> CreateInternalAsync(
            Stream? stream,
            IServiceProvider hostProvidedServices,
            IServiceBroker? serviceBroker)
        {
            var traceSource = (TraceSource?)hostProvidedServices.GetService(typeof(TraceSource));
 
            // RazorBrokeredServiceData is a hook that can be provided for different host scenarios, such as testing.
            var brokeredServiceData = (RazorBrokeredServiceData?)hostProvidedServices.GetService(typeof(RazorBrokeredServiceData));
 
            var exportProvider = brokeredServiceData?.ExportProvider
                ?? await RemoteMefComposition.GetSharedExportProviderAsync(CancellationToken.None).ConfigureAwait(false);
 
            // There are three logging cases:
            //
            // 1. We've been provided an ILoggerFactory from the host.
            // 2. We've been provided a TraceSource and create an ILoggerFactory for that.
            // 3. We don't have anything and just use the empty ILoggerFactory.
 
            var targetLoggerFactory = brokeredServiceData?.LoggerFactory
                ?? (traceSource is not null
                    ? new TraceSourceLoggerFactory(traceSource)
                    : EmptyLoggerFactory.Instance);
 
            var workspaceProvider = brokeredServiceData?.WorkspaceProvider ?? RemoteWorkspaceProvider.Instance;
 
            // Update the MEF composition's IHostServicesAccessor to the target workspace
            var hostServicesProvider = exportProvider.GetExportedValue<RemoteHostServicesProvider>();
            hostServicesProvider.SetWorkspaceProvider(workspaceProvider);
 
            // Update the MEF composition's ILoggerFactory to the target ILoggerFactory.
            // Note that this means that the first non-empty ILoggerFactory that we use
            // will be used for MEF component logging for the lifetime of all services.
            var remoteLoggerFactory = exportProvider.GetExportedValue<RemoteLoggerFactory>();
            var didSetLoggerFactory = remoteLoggerFactory.SetTargetLoggerFactory(targetLoggerFactory);
 
            // In proc services don't use any service hub infra
            if (stream is null)
            {
                var inProcArgs = new ServiceArgs(ServiceBroker: null, exportProvider, targetLoggerFactory, workspaceProvider, ServerConnection: null, brokeredServiceData.AssumeNotNull().Interceptor);
                return CreateService(in inProcArgs);
            }
 
            // At this point, we know we're in a remote scenario where we're on the end of a service hub connection, so we want
            // logged errors to be thrown, so they're bubbled up to the client.
            if (didSetLoggerFactory)
            {
                remoteLoggerFactory.AddLoggerProvider(new ThrowingErrorLoggerProvider());
            }
 
            var serverConnection = CreateServerConnection(stream, traceSource);
 
            var args = new ServiceArgs(serviceBroker.AssumeNotNull(), exportProvider, targetLoggerFactory, workspaceProvider, serverConnection, brokeredServiceData?.Interceptor);
            var service = CreateService(in args);
 
            ConnectService(serverConnection, service);
 
            return service;
        }
 
        protected static ServiceRpcDescriptor.RpcConnection CreateServerConnection(Stream stream, TraceSource? traceSource)
        {
            var pipe = stream.UsePipe();
 
            var descriptor = typeof(IRemoteJsonService).IsAssignableFrom(typeof(TService))
                ? RazorServices.JsonDescriptors.GetDescriptorForServiceFactory(typeof(TService))
                : RazorServices.Descriptors.GetDescriptorForServiceFactory(typeof(TService));
            var serverConnection = descriptor.WithTraceSource(traceSource).ConstructRpcConnection(pipe);
            return serverConnection;
        }
 
        protected static void ConnectService(ServiceRpcDescriptor.RpcConnection serverConnection, TService service)
        {
            serverConnection.AddLocalRpcTarget(service);
            serverConnection.StartListening();
        }
    }
}