Rough outline of hosting components

This commit is contained in:
Louis DeJardin 2014-02-09 23:25:56 -08:00
parent c37c555333
commit 0e813fbb79
30 changed files with 787 additions and 0 deletions

43
Hosting.sln Normal file
View File

@ -0,0 +1,43 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.21005.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E0497F39-AFFB-4819-A116-E39E361915AB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{FEB39027-9158-4DE2-997F-7ADAEF8188D0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Hosting.net45", "src\Microsoft.AspNet.Hosting\Microsoft.AspNet.Hosting.net45.csproj", "{D546290B-E280-4D99-BA9C-0D364A0AFB54}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Hosting.k10", "src\Microsoft.AspNet.Hosting\Microsoft.AspNet.Hosting.k10.csproj", "{DBB72F0F-755D-41CF-8FE2-F4B6AE214E91}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Hosting.Tests.net45", "test\Microsoft.AspNet.Hosting.Tests\Microsoft.AspNet.Hosting.Tests.net45.csproj", "{80588AF3-6B14-4D11-9DC4-1EF6453B54C9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D546290B-E280-4D99-BA9C-0D364A0AFB54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D546290B-E280-4D99-BA9C-0D364A0AFB54}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D546290B-E280-4D99-BA9C-0D364A0AFB54}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D546290B-E280-4D99-BA9C-0D364A0AFB54}.Release|Any CPU.Build.0 = Release|Any CPU
{DBB72F0F-755D-41CF-8FE2-F4B6AE214E91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DBB72F0F-755D-41CF-8FE2-F4B6AE214E91}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBB72F0F-755D-41CF-8FE2-F4B6AE214E91}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBB72F0F-755D-41CF-8FE2-F4B6AE214E91}.Release|Any CPU.Build.0 = Release|Any CPU
{80588AF3-6B14-4D11-9DC4-1EF6453B54C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{80588AF3-6B14-4D11-9DC4-1EF6453B54C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{80588AF3-6B14-4D11-9DC4-1EF6453B54C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{80588AF3-6B14-4D11-9DC4-1EF6453B54C9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D546290B-E280-4D99-BA9C-0D364A0AFB54} = {E0497F39-AFFB-4819-A116-E39E361915AB}
{DBB72F0F-755D-41CF-8FE2-F4B6AE214E91} = {E0497F39-AFFB-4819-A116-E39E361915AB}
{80588AF3-6B14-4D11-9DC4-1EF6453B54C9} = {FEB39027-9158-4DE2-997F-7ADAEF8188D0}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,20 @@
using System;
using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Hosting.Builder
{
public class BuilderFactory : IBuilderFactory
{
private readonly IServiceProvider _serviceProvider;
public BuilderFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IBuilder CreateBuilder()
{
return new PipelineCore.Builder(_serviceProvider);
}
}
}

View File

@ -0,0 +1,18 @@
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.FeatureModel;
using Microsoft.AspNet.Hosting.Server;
using Microsoft.AspNet.PipelineCore;
namespace Microsoft.AspNet.Hosting.Builder
{
public class HttpContextFactory : IHttpContextFactory
{
public HttpContext CreateHttpContext(object serverContext)
{
var featureObject = serverContext as IFeatureCollection ?? new FeatureObject(serverContext);
var featureCollection = new FeatureCollection(featureObject);
var httpContext = new DefaultHttpContext(featureCollection);
return httpContext;
}
}
}

View File

@ -0,0 +1,9 @@
using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Hosting.Builder
{
public interface IBuilderFactory
{
IBuilder CreateBuilder();
}
}

View File

@ -0,0 +1,9 @@
using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Hosting.Builder
{
public interface IHttpContextFactory
{
HttpContext CreateHttpContext(object serverContext);
}
}

View File

@ -0,0 +1,20 @@
using System;
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.Hosting.Server;
namespace Microsoft.AspNet.Hosting
{
public class HostingContext
{
public IServiceProvider Services { get; set; }
public IBuilder Builder { get; set; }
public string ApplicationName { get; set; }
public Action<IBuilder> ApplicationStartup { get; set; }
public RequestDelegate ApplicationDelegate { get; set; }
public string ServerName { get; set; }
public IServerFactory ServerFactory { get; set; }
}
}

View File

@ -0,0 +1,104 @@
using System;
using System.Threading;
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.Hosting.Builder;
using Microsoft.AspNet.Hosting.Startup;
using Microsoft.AspNet.Hosting.Server;
namespace Microsoft.AspNet.Hosting
{
public class HostingEngine : IHostingEngine
{
private readonly IServerManager _serverManager;
private readonly IStartupManager _startupManager;
private readonly IBuilderFactory _builderFactory;
private readonly IHttpContextFactory _httpContextFactory;
public HostingEngine(
IServerManager serverManager,
IStartupManager startupManager,
IBuilderFactory builderFactory,
IHttpContextFactory httpContextFactory)
{
_serverManager = serverManager;
_startupManager = startupManager;
_builderFactory = builderFactory;
_httpContextFactory = httpContextFactory;
}
public IDisposable Start(HostingContext context)
{
EnsureBuilder(context);
EnsureServerFactory(context);
EnsureApplicationDelegate(context);
var pipeline = new PipelineInstance(_httpContextFactory, context.ApplicationDelegate);
var server = context.ServerFactory.Start(pipeline.Invoke);
return new Disposable(() =>
{
server.Dispose();
pipeline.Dispose();
});
}
private void EnsureBuilder(HostingContext context)
{
if (context.Builder != null)
{
return;
}
context.Builder = _builderFactory.CreateBuilder();
}
private void EnsureServerFactory(HostingContext context)
{
if (context.ServerFactory != null)
{
return;
}
context.ServerFactory = _serverManager.GetServer(context.ServerName);
}
private void EnsureApplicationDelegate(HostingContext context)
{
if (context.ApplicationDelegate != null)
{
return;
}
EnsureApplicationStartup(context);
EnsureBuilder(context);
context.ApplicationStartup.Invoke(context.Builder);
context.ApplicationDelegate = context.Builder.Build();
}
private void EnsureApplicationStartup(HostingContext context)
{
if (context.ApplicationStartup != null)
{
return;
}
context.ApplicationStartup = _startupManager.LoadStartup(context.ApplicationName);
}
private class Disposable : IDisposable
{
private Action _dispose;
public Disposable(Action dispose)
{
_dispose = dispose;
}
public void Dispose()
{
Interlocked.Exchange(ref _dispose, () => { }).Invoke();
}
}
}
}

View File

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Configuration;
using Microsoft.AspNet.DependencyInjection;
using Microsoft.AspNet.Hosting.Builder;
using Microsoft.AspNet.Hosting.Startup;
using Microsoft.AspNet.Hosting.Server;
namespace Microsoft.AspNet.Hosting
{
public static class HostingServices
{
public static IEnumerable<IServiceDescriptor> DefaultServices()
{
return DefaultServices(new EmptyConfiguration());
}
public static IEnumerable<IServiceDescriptor> DefaultServices(IConfiguration configuration)
{
yield return DescribeService<IHostingEngine, HostingEngine>(configuration);
yield return DescribeService<IServerFactoryProvider, ServerFactoryProvider>(configuration);
yield return DescribeService<IStartupManager, StartupManager>(configuration);
yield return DescribeService<IStartupLoaderProvider, StartupLoaderProvider>(configuration);
yield return DescribeService<IBuilderFactory, BuilderFactory>(configuration);
yield return DescribeService<IHttpContextFactory, HttpContextFactory>(configuration);
}
public static IServiceDescriptor DescribeService<TService, TImplementation>(IConfiguration configuration,
LifecycleKind lifecycle = LifecycleKind.Transient)
{
return DescribeService(typeof(TService), typeof(TImplementation), configuration, lifecycle);
}
public static IServiceDescriptor DescribeService(
Type serviceType,
Type implementationType,
IConfiguration configuration,
LifecycleKind lifecycle)
{
var serviceTypeName = serviceType.FullName;
var implemenationTypeName = configuration.Get(serviceTypeName);
if (!String.IsNullOrEmpty(implemenationTypeName))
{
try
{
implementationType = Type.GetType(implemenationTypeName);
}
catch (Exception ex)
{
throw new Exception(string.Format("TODO: unable to locate implementation {0} for service {1}", implemenationTypeName, serviceTypeName), ex);
}
}
return new ServiceTypeDescriptor(serviceType, implementationType, lifecycle);
}
public class EmptyConfiguration : IConfiguration
{
public string Get(string key)
{
return null;
}
}
public class ServiceTypeDescriptor : IServiceDescriptor
{
public ServiceTypeDescriptor(Type serviceType, Type implementationType, LifecycleKind lifecycle)
{
ServiceType = serviceType;
ImplementationType = implementationType;
Lifecycle = lifecycle;
}
public LifecycleKind Lifecycle { get; private set; }
public Type ServiceType { get; private set; }
public Type ImplementationType { get; private set; }
public object ImplementationInstance { get; private set; }
}
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace Microsoft.AspNet.Hosting
{
public interface IHostingEngine
{
IDisposable Start(HostingContext context);
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.Hosting.Builder;
using Microsoft.AspNet.Hosting.Server;
namespace Microsoft.AspNet.Hosting
{
public class PipelineInstance : IDisposable
{
private readonly IHttpContextFactory _httpContextFactory;
private readonly RequestDelegate _requestDelegate;
public PipelineInstance(IHttpContextFactory httpContextFactory, RequestDelegate requestDelegate)
{
_httpContextFactory = httpContextFactory;
_requestDelegate = requestDelegate;
}
public Task Invoke(object serverEnvironment)
{
var httpContext = _httpContextFactory.CreateHttpContext(serverEnvironment);
return _requestDelegate(httpContext);
}
public void Dispose()
{
// TODO: application notification of disposal
}
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Hosting.Server
{
public interface IServerFactory
{
IDisposable Start(Func<object, Task> application);
}
}

View File

@ -0,0 +1,7 @@
namespace Microsoft.AspNet.Hosting.Server
{
public interface IServerFactoryProvider
{
IServerFactory GetServerFactory(string serverName);
}
}

View File

@ -0,0 +1,7 @@
namespace Microsoft.AspNet.Hosting.Server
{
public interface IServerManager
{
IServerFactory GetServer(string serverName);
}
}

View File

@ -0,0 +1,10 @@
namespace Microsoft.AspNet.Hosting.Server
{
public class ServerManager : IServerManager
{
public IServerFactory GetServer(string serverName)
{
throw new System.NotImplementedException();
}
}
}

View File

@ -0,0 +1,13 @@
using System;
using Microsoft.AspNet.Hosting.Server;
namespace Microsoft.AspNet.Hosting
{
public class ServerFactoryProvider : IServerFactoryProvider
{
public IServerFactory GetServerFactory(string serverFactoryIdentifier)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Hosting.Startup
{
public interface IStartupLoader
{
Action<IBuilder> LoadStartup(string applicationName, IList<string> diagnosticMessages);
}
}

View File

@ -0,0 +1,9 @@
namespace Microsoft.AspNet.Hosting.Startup
{
public interface IStartupLoaderProvider
{
int Order { get; }
IStartupLoader CreateStartupLoader(IStartupLoader next);
}
}

View File

@ -0,0 +1,10 @@
using System;
using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Hosting.Startup
{
public interface IStartupManager
{
Action<IBuilder> LoadStartup(string applicationName);
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Hosting.Startup
{
public class NullStartupLoader : IStartupLoader
{
static NullStartupLoader()
{
Instance = new NullStartupLoader();
}
public static IStartupLoader Instance { get; private set; }
public Action<IBuilder> LoadStartup(string applicationName, IList<string> diagnosticMessages)
{
return null;
}
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.DependencyInjection;
namespace Microsoft.AspNet.Hosting.Startup
{
public class StartupLoader : IStartupLoader
{
private readonly IServiceProvider _services;
private readonly IStartupLoader _next;
public StartupLoader(IServiceProvider services, IStartupLoader next)
{
_services = services;
_next = next;
}
public Action<IBuilder> LoadStartup(string applicationName, IList<string> diagnosticMessages)
{
if (String.IsNullOrWhiteSpace(applicationName))
{
return _next.LoadStartup(applicationName, diagnosticMessages);
}
var parts = applicationName.Split(new[] { ',' }, 2);
if (parts.Length == 2)
{
var typeName = parts[0];
var assemblyName = parts[1];
var assembly = Assembly.Load(new AssemblyName(assemblyName));
if (assembly == null)
{
throw new Exception(String.Format("TODO: assembly {0} failed to load message", assemblyName));
}
var type = assembly.GetType(typeName);
if (type == null)
{
throw new Exception(String.Format("TODO: type {0} failed to load message", typeName));
}
var methodInfo = type.GetTypeInfo().GetDeclaredMethod("Configuration");
if (methodInfo == null)
{
throw new Exception("TODO: Configuration method not found");
}
object instance = null;
if (!methodInfo.IsStatic)
{
instance = ActivatorUtilities.GetServiceOrCreateInstance(_services, type);
}
return builder => methodInfo.Invoke(instance, new object[] { builder });
}
throw new Exception("TODO: Unrecognized format");
}
}
}

View File

@ -0,0 +1,21 @@
using System;
namespace Microsoft.AspNet.Hosting.Startup
{
public class StartupLoaderProvider : IStartupLoaderProvider
{
private readonly IServiceProvider _services;
public StartupLoaderProvider(IServiceProvider services)
{
_services = services;
}
public int Order { get { return -100; } }
public IStartupLoader CreateStartupLoader(IStartupLoader next)
{
return new StartupLoader(_services, next);
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Hosting.Startup
{
public class StartupManager : IStartupManager
{
private readonly IEnumerable<IStartupLoaderProvider> _providers;
public StartupManager(IEnumerable<IStartupLoaderProvider> providers)
{
_providers = providers;
}
public Action<IBuilder> LoadStartup(string applicationName)
{
// build ordered chain of application loaders
var chain = _providers
.OrderBy(provider => provider.Order)
.Aggregate(NullStartupLoader.Instance, (next, provider) => provider.CreateStartupLoader(next));
// invoke chain to acquire application entrypoint and diagnostic messages
var diagnosticMessages = new List<string>();
var application = chain.LoadStartup(applicationName, diagnosticMessages);
if (application == null)
{
throw new Exception(diagnosticMessages.Aggregate("TODO: web application entrypoint not found message", (a, b) => a + "\r\n" + b));
}
return application;
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using Microsoft.AspNet.DependencyInjection;
namespace Microsoft.AspNet.Hosting
{
public static class WebApplication
{
public static IDisposable Start()
{
var context = new HostingContext
{
Services = new ServiceProvider().Add(HostingServices.DefaultServices())
};
var engine = context.Services.GetService<IHostingEngine>();
if (engine == null)
{
throw new Exception("TODO: IHostingEngine service not available exception");
}
return engine.Start(context);
}
}
}

View File

@ -0,0 +1,14 @@
{
"version": "0.1-alpha-*",
"dependencies":{
"Microsoft.AspNet.DependencyInjection":"0.1-alpha-*",
"Microsoft.AspNet.Configuration":"0.1-alpha-*",
"Microsoft.AspNet.PipelineCore":"0.1-alpha-*",
"Microsoft.AspNet.Abstractions":"0.1-alpha-*",
"Microsoft.AspNet.FeatureModel":"0.1-alpha-*"
},
"configurations": {
"net45" : {},
"k10" : {}
}
}

View File

@ -0,0 +1,11 @@
using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Hosting.Tests.Fakes
{
public class FakeStartup
{
public void Configuration(IBuilder builder)
{
}
}
}

View File

@ -0,0 +1,19 @@
using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Hosting.Tests.Fakes
{
public class FakeStartupWithServices
{
private readonly IFakeStartupCallback _fakeStartupCallback;
public FakeStartupWithServices(IFakeStartupCallback fakeStartupCallback)
{
_fakeStartupCallback = fakeStartupCallback;
}
public void Configuration(IBuilder builder)
{
_fakeStartupCallback.ConfigurationMethodCalled(this);
}
}
}

View File

@ -0,0 +1,7 @@
namespace Microsoft.AspNet.Hosting.Tests.Fakes
{
public interface IFakeStartupCallback
{
void ConfigurationMethodCalled(object instance);
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.DependencyInjection;
using Microsoft.AspNet.Hosting.Server;
using Xunit;
namespace Microsoft.AspNet.Hosting.Tests
{
public class HostingEngineTests : IServerManager, IServerFactory
{
private readonly IList<StartInstance> _startInstances = new List<StartInstance>();
[Fact]
public void HostingEngineCanBeResolvedWithDefaultServices()
{
var services = new ServiceProvider()
.Add(HostingServices.DefaultServices());
var engine = services.GetService<IHostingEngine>();
Assert.NotNull(engine);
}
[Fact]
public void HostingEngineCanBeStarted()
{
var services = new ServiceProvider()
.Add(HostingServices.DefaultServices()
.Where(descriptor => descriptor.ServiceType != typeof(IServerManager)))
.AddInstance<IServerManager>(this);
var engine = services.GetService<IHostingEngine>();
var context = new HostingContext
{
ApplicationName = "Microsoft.AspNet.Hosting.Tests.Fakes.FakeStartup, Microsoft.AspNet.Hosting.Tests"
};
var engineStart = engine.Start(context);
Assert.NotNull(engineStart);
Assert.Equal(1, _startInstances.Count);
Assert.Equal(0, _startInstances[0].DisposeCalls);
engineStart.Dispose();
Assert.Equal(1, _startInstances[0].DisposeCalls);
}
public IServerFactory GetServer(string serverName)
{
return this;
}
public void Initialize(IBuilder builder)
{
}
public IDisposable Start(Func<object, Task> application)
{
var startInstance = new StartInstance(application);
_startInstances.Add(startInstance);
return startInstance;
}
public class StartInstance : IDisposable
{
private readonly Func<object, Task> _application;
public StartInstance(Func<object, Task> application)
{
_application = application;
}
public int DisposeCalls { get; set; }
public void Dispose()
{
DisposeCalls += 1;
}
}
}
}

View File

@ -0,0 +1,51 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.DependencyInjection;
using Microsoft.AspNet.Hosting.Startup;
using Microsoft.AspNet.Hosting.Tests.Fakes;
using Xunit;
namespace Microsoft.AspNet.Hosting.Tests
{
public class StartupManagerTests : IFakeStartupCallback
{
private readonly IList<object> _configurationMethodCalledList = new List<object>();
[Fact]
public void DefaultServicesLocateStartupByNameAndNamespace()
{
IServiceProvider services = new ServiceProvider().Add(HostingServices.DefaultServices());
var manager = services.GetService<IStartupManager>();
var startup = manager.LoadStartup("Microsoft.AspNet.Hosting.Tests.Fakes.FakeStartup, Microsoft.AspNet.Hosting.Tests");
Assert.IsType<StartupManager>(manager);
Assert.NotNull(startup);
}
[Fact]
public void StartupClassMayHaveHostingServicesInjected()
{
IServiceProvider services = new ServiceProvider()
.Add(HostingServices.DefaultServices())
.AddInstance<IFakeStartupCallback>(this);
var manager = services.GetService<IStartupManager>();
var startup = manager.LoadStartup("Microsoft.AspNet.Hosting.Tests.Fakes.FakeStartupWithServices, Microsoft.AspNet.Hosting.Tests");
startup.Invoke(null);
Assert.Equal(1, _configurationMethodCalledList.Count);
}
public void ConfigurationMethodCalled(object instance)
{
_configurationMethodCalledList.Add(instance);
}
}
}

View File

@ -0,0 +1,12 @@
{
"version": "0.1-alpha-*",
"dependencies": {
"xunit": "1.9.2",
"Microsoft.AspNet.Hosting":"",
"Microsoft.AspNet.Abstractions":"0.1-alpha-*",
"Microsoft.AspNet.DependencyInjection":"0.1-alpha-*"
},
"configurations": {
"net45": {}
}
}