Startup: Call ConfigureServices if exists

This commit is contained in:
Hao Kung 2014-10-03 18:23:50 -07:00
parent 6826eb183c
commit 53eea70c06
6 changed files with 214 additions and 44 deletions

View File

@ -20,8 +20,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.DependencyInjection.Fallback;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Hosting.Startup
{
@ -38,6 +39,66 @@ namespace Microsoft.AspNet.Hosting.Startup
_next = next;
}
private MethodInfo FindMethod(Type startupType, string methodName, string environmentName, Type returnType = null, bool required = true)
{
var methodNameWithEnv = methodName + environmentName;
var methodInfo = startupType.GetTypeInfo().GetDeclaredMethod(methodNameWithEnv)
?? startupType.GetTypeInfo().GetDeclaredMethod(methodName);
if (methodInfo == null)
{
if (required)
{
throw new Exception(string.Format("TODO: {0} or {1} method not found",
methodNameWithEnv,
methodName));
}
return null;
}
if (returnType != null && methodInfo.ReturnType != returnType)
{
throw new Exception(string.Format("TODO: {0} method does not return " + returnType.Name,
methodInfo.Name));
}
return methodInfo;
}
private void Invoke(MethodInfo methodInfo, object instance, IApplicationBuilder builder, IServiceCollection services = null)
{
var parameterInfos = methodInfo.GetParameters();
var parameters = new object[parameterInfos.Length];
for (var index = 0; index != parameterInfos.Length; ++index)
{
var parameterInfo = parameterInfos[index];
if (parameterInfo.ParameterType == typeof(IApplicationBuilder))
{
parameters[index] = builder;
}
else if (services != null && parameterInfo.ParameterType == typeof(IServiceCollection))
{
parameters[index] = services;
}
else
{
try
{
parameters[index] = _services.GetService(parameterInfo.ParameterType);
}
catch (Exception)
{
throw new Exception(string.Format(
"TODO: Unable to resolve service for {0} method {1} {2}",
methodInfo.Name,
parameterInfo.Name,
parameterInfo.ParameterType.FullName));
}
}
}
methodInfo.Invoke(instance, parameters);
}
public Action<IApplicationBuilder> LoadStartup(
string applicationName,
string environmentName,
@ -81,65 +142,34 @@ namespace Microsoft.AspNet.Hosting.Startup
if (type == null)
{
throw new Exception(String.Format("TODO: {0} or {1} class not found in assembly {2}",
throw new Exception(String.Format("TODO: {0} or {1} class not found in assembly {2}",
startupName1,
startupName2,
applicationName));
}
var configureMethod1 = "Configure" + environmentName;
var configureMethod2 = "Configure";
var methodInfo = type.GetTypeInfo().GetDeclaredMethod(configureMethod1);
if (methodInfo == null)
{
methodInfo = type.GetTypeInfo().GetDeclaredMethod(configureMethod2);
}
if (methodInfo == null)
{
throw new Exception(string.Format("TODO: {0} or {1} method not found",
configureMethod1,
configureMethod2));
}
if (methodInfo.ReturnType != typeof(void))
{
throw new Exception(string.Format("TODO: {0} method isn't void-returning.",
methodInfo.Name));
}
var configureMethod = FindMethod(type, "Configure", environmentName, typeof(void), required: true);
// TODO: accept IServiceProvider method as well?
var servicesMethod = FindMethod(type, "ConfigureServices", environmentName, typeof(void), required: false);
object instance = null;
if (!methodInfo.IsStatic)
if (!configureMethod.IsStatic || (servicesMethod != null && !servicesMethod.IsStatic))
{
instance = ActivatorUtilities.GetServiceOrCreateInstance(_services, type);
}
return builder =>
{
var parameterInfos = methodInfo.GetParameters();
var parameters = new object[parameterInfos.Length];
for (var index = 0; index != parameterInfos.Length; ++index)
if (servicesMethod != null)
{
var parameterInfo = parameterInfos[index];
if (parameterInfo.ParameterType == typeof(IApplicationBuilder))
var services = new ServiceCollection();
services.Add(OptionsServices.GetDefaultServices());
Invoke(servicesMethod, instance, builder, services);
if (builder != null)
{
parameters[index] = builder;
}
else
{
try
{
parameters[index] = _services.GetService(parameterInfo.ParameterType);
}
catch (Exception ex)
{
throw new Exception(string.Format(
"TODO: Unable to resolve service for startup method {0} {1}",
parameterInfo.Name,
parameterInfo.ParameterType.FullName));
}
builder.ApplicationServices = services.BuildServiceProvider(builder.ApplicationServices);
}
}
methodInfo.Invoke(instance, parameters);
Invoke(configureMethod, instance, builder);
};
}
}

View File

@ -8,6 +8,7 @@
"Microsoft.Framework.ConfigurationModel": "1.0.0-*",
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
"Microsoft.Framework.Logging": "1.0.0-*",
"Microsoft.Framework.OptionsModel": "1.0.0-*",
"Microsoft.Framework.Runtime.Interfaces": { "version": "1.0.0-*", "type": "build" },
"Newtonsoft.Json": "6.0.4"
},

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
// NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing
// permissions and limitations under the License.
namespace Microsoft.AspNet.Hosting.Fakes
{
public class FakeOptions
{
public bool Configured { get; set; }
public string Environment { get; set; }
}
}

View File

@ -17,6 +17,7 @@
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Hosting.Fakes
{
@ -26,6 +27,39 @@ namespace Microsoft.AspNet.Hosting.Fakes
{
}
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureOptions<FakeOptions>(o => o.Configured = true);
}
public void ConfigureServicesDev(IServiceCollection services)
{
services.ConfigureOptions<FakeOptions>(o =>
{
o.Configured = true;
o.Environment = "Dev";
});
}
public void ConfigureServicesRetail(IServiceCollection services)
{
services.ConfigureOptions<FakeOptions>(o =>
{
o.Configured = true;
o.Environment = "Retail";
});
}
public static void ConfigureServicesStatic(IServiceCollection services)
{
services.ConfigureOptions<FakeOptions>(o =>
{
o.Configured = true;
o.Environment = "Static";
});
}
public void Configure(IApplicationBuilder builder)
{
}

View File

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
// NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing
// permissions and limitations under the License.
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Hosting.Fakes
{
public class StartupNoServices
{
public StartupNoServices()
{
}
public void Configure(IApplicationBuilder builder)
{
}
}
}

View File

@ -21,6 +21,9 @@ using Microsoft.AspNet.Hosting.Startup;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.DependencyInjection.Fallback;
using Xunit;
using Microsoft.Framework.OptionsModel;
using Microsoft.AspNet.Builder;
using System;
namespace Microsoft.AspNet.Hosting
{
@ -46,6 +49,49 @@ namespace Microsoft.AspNet.Hosting
Assert.Equal(2, _configurationMethodCalledList.Count);
}
[Theory]
[InlineData(null)]
[InlineData("Dev")]
[InlineData("Retail")]
[InlineData("Static")]
public void StartupClassAddsConfigureServicesToApplicationServices(string environment)
{
var serviceCollection = new ServiceCollection();
serviceCollection.Add(HostingServices.GetDefaultServices());
var services = serviceCollection.BuildServiceProvider();
var manager = services.GetService<IStartupManager>();
var startup = manager.LoadStartup("Microsoft.AspNet.Hosting.Tests", environment ?? "");
var app = new ApplicationBuilder(services);
startup.Invoke(app);
var options = app.ApplicationServices.GetService<IOptionsAccessor<FakeOptions>>().Options;
Assert.NotNull(options);
Assert.True(options.Configured);
Assert.Equal(environment, options.Environment);
}
[Fact]
public void StartupClassDoesNotRegisterOptionsWithNoConfigureServices()
{
var serviceCollection = new ServiceCollection();
serviceCollection.Add(HostingServices.GetDefaultServices());
serviceCollection.AddInstance<IFakeStartupCallback>(this);
var services = serviceCollection.BuildServiceProvider();
var manager = services.GetService<IStartupManager>();
var startup = manager.LoadStartup("Microsoft.AspNet.Hosting.Tests", "NoServices");
var app = new ApplicationBuilder(services);
startup.Invoke(app);
var ex = Assert.Throws<Exception>(() => app.ApplicationServices.GetService<IOptionsAccessor<FakeOptions>>());
Assert.True(ex.Message.Contains("No service for type 'Microsoft.Framework.OptionsModel.IOptionsAccessor"));
}
public void ConfigurationMethodCalled(object instance)
{
_configurationMethodCalledList.Add(instance);