DryIoc.WebApi 设置
DryIoc.WebApi Setup
我正在探索 DryIoc 在 .NET WebAPI 应用程序中的使用,并注意到初始化步骤有一个奇怪的行为。在一个简单的测试 webapi 应用程序中,我有以下 DryIoc 注册 class,它在 WebApi 配置注册后立即被调用。
public class DryIocConfig
{
public static void Register(HttpConfiguration config)
{
var c = new Container().WithWebApi(config);
c.Register<IWidgetService, WidgetService>(Reuse.Singleton);
c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton);
}
}
以及以下 WebApi 控制器:
public class ValuesController : ApiController
{
private readonly IWidgetService _widgetService;
public ValuesController(IWidgetService widgetService)
{
_widgetService = widgetService;
}
// GET api/values
public IEnumerable<Widget> Get()
{
return _widgetService.GetWidgets();
}
}
这似乎工作正常,但在我看来是相同的,但写得有点冗长,代码我得到一个错误。
public class DryIocConfig
{
public static void Register(HttpConfiguration config)
{
var c = new Container();
c.WithWebApi(config); // Now separate statement rather than chained.
c.Register<IWidgetService, WidgetService>(Reuse.Singleton);
c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton);
}
}
我得到的异常如下(如JSON):
{
"Message" : "An error has occurred.",
"ExceptionMessage" : "An error occurred when trying to create a controller of type 'ValuesController'. Make sure that the controller has a parameterless public constructor.",
"ExceptionType" : "System.InvalidOperationException",
"StackTrace" : " at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()",
"InnerException" : {
"Message" : "An error has occurred.",
"ExceptionMessage" : "Type 'IOCContainerTest.DryLoc.Controllers.ValuesController' does not have a default constructor",
"ExceptionType" : "System.ArgumentException",
"StackTrace" : " at System.Linq.Expressions.Expression.New(Type type)\r\n at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"
}
}
DryIoc 有什么奇怪的地方吗?或者这是我从未遇到过的 C# 细微差别?
这是因为 .WithWebApi()
是 source 的扩展方法。
public static IContainer WithWebApi(this IContainer container, HttpConfiguration config,
IEnumerable<Assembly> controllerAssemblies = null, IScopeContext scopeContext = null,
Func<Type, bool> throwIfUnresolved = null)
{
container.ThrowIfNull();
if (container.ScopeContext == null)
container = container.With(scopeContext: scopeContext ?? new AsyncExecutionFlowScopeContext());
container.RegisterWebApiControllers(config, controllerAssemblies);
container.SetFilterProvider(config.Services);
InsertRegisterRequestMessageHandler(config);
config.DependencyResolver = new DryIocDependencyResolver(container, throwIfUnresolved);
return container;
}
在第一个语法示例中,您创建了 Container
的新实例并将该新创建的实例传递给 .WithWebApi()
。这反过来会更新容器实例,最后 returns 它回到变量 c
.
在第二个语法示例中,您永远不会将扩展方法 BACK 的值返回到原始变量,更新它。您调用它就好像它是一个 void 方法,在这种情况下它什么都不做,因此是异常。
如果你改为写:
var c = new Container();
c = c.WithWebApi(config);
它本质上是第一种语法的更详细的示例,并且会使用新功能正确更新 c
。
这看起来不错,应该可以。显然,从 ccontainer.WithWebApi(config);
返回的容器不是原始容器,这在这一点上是出乎意料的,因此至少有代码味道,因为它会引起可能的错误。最好编写几个单元测试并在 DryIoC Bitbucket site 上提出一个问题。
为了帮助您,这里有一个如何编写此类测试的模板。创建一个新的测试项目并执行以下操作:
安装 NuGet 包
Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
Install-Package DryIoc.WebApi.Owin.dll
Install-Package Xunit
控制器
public sealed class ValuesController : ApiController
{
private IWidgetService WidgetService { get; }
public ValuesController(IWidgetService widgetService)
{
if(widgetService == null)
throw new ArgumentNullException(nameof(widgetService));
WidgetService = widgetService;
}
[HttpGet]
public IEnumerable<Widget> Get()
{
return WidgetService.GetWidgets().ToArray();
}
}
OWIN 启动
public sealed class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration()
.ConfigureRouting()
.ConfigureDependencyInjection();
app.UseWebApi(config);
// protip: use OWIN error page instead of ASP.NET yellow pages for better diagnostics
// Install-Package Microsoft.Owin.Diagnostics
// app.UseErrorPage(ErrorPageOptions.ShowAll);
}
private static HttpConfiguration ConfigureRouting(this HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "default",
routeTemplate: "/",
defaults: new { controller = "values" });
return config;
}
private static HttpConfiguration ConfigureDependencyInjection(this HttpConfiguration config)
{
new Container()
.Register<IWidgetService, WidgetService>(Reuse.Singleton)
.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton)
.WithWebApi(config);
return config;
}
}
单元测试
[Fact]
public async Task ShouldFoo()
{
const string baseAddress = "http://localhost:8000";
using (WebApp.Start<Startup>(baseAddress))
{
var httpclient = new HttpClient
{
BaseAddress = new Uri(baseAddress)
};
var response = await httpclient.GetAsync("/");
Assert.That(...);
}
}
我正在探索 DryIoc 在 .NET WebAPI 应用程序中的使用,并注意到初始化步骤有一个奇怪的行为。在一个简单的测试 webapi 应用程序中,我有以下 DryIoc 注册 class,它在 WebApi 配置注册后立即被调用。
public class DryIocConfig
{
public static void Register(HttpConfiguration config)
{
var c = new Container().WithWebApi(config);
c.Register<IWidgetService, WidgetService>(Reuse.Singleton);
c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton);
}
}
以及以下 WebApi 控制器:
public class ValuesController : ApiController
{
private readonly IWidgetService _widgetService;
public ValuesController(IWidgetService widgetService)
{
_widgetService = widgetService;
}
// GET api/values
public IEnumerable<Widget> Get()
{
return _widgetService.GetWidgets();
}
}
这似乎工作正常,但在我看来是相同的,但写得有点冗长,代码我得到一个错误。
public class DryIocConfig
{
public static void Register(HttpConfiguration config)
{
var c = new Container();
c.WithWebApi(config); // Now separate statement rather than chained.
c.Register<IWidgetService, WidgetService>(Reuse.Singleton);
c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton);
}
}
我得到的异常如下(如JSON):
{
"Message" : "An error has occurred.",
"ExceptionMessage" : "An error occurred when trying to create a controller of type 'ValuesController'. Make sure that the controller has a parameterless public constructor.",
"ExceptionType" : "System.InvalidOperationException",
"StackTrace" : " at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()",
"InnerException" : {
"Message" : "An error has occurred.",
"ExceptionMessage" : "Type 'IOCContainerTest.DryLoc.Controllers.ValuesController' does not have a default constructor",
"ExceptionType" : "System.ArgumentException",
"StackTrace" : " at System.Linq.Expressions.Expression.New(Type type)\r\n at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"
}
}
DryIoc 有什么奇怪的地方吗?或者这是我从未遇到过的 C# 细微差别?
这是因为 .WithWebApi()
是 source 的扩展方法。
public static IContainer WithWebApi(this IContainer container, HttpConfiguration config,
IEnumerable<Assembly> controllerAssemblies = null, IScopeContext scopeContext = null,
Func<Type, bool> throwIfUnresolved = null)
{
container.ThrowIfNull();
if (container.ScopeContext == null)
container = container.With(scopeContext: scopeContext ?? new AsyncExecutionFlowScopeContext());
container.RegisterWebApiControllers(config, controllerAssemblies);
container.SetFilterProvider(config.Services);
InsertRegisterRequestMessageHandler(config);
config.DependencyResolver = new DryIocDependencyResolver(container, throwIfUnresolved);
return container;
}
在第一个语法示例中,您创建了 Container
的新实例并将该新创建的实例传递给 .WithWebApi()
。这反过来会更新容器实例,最后 returns 它回到变量 c
.
在第二个语法示例中,您永远不会将扩展方法 BACK 的值返回到原始变量,更新它。您调用它就好像它是一个 void 方法,在这种情况下它什么都不做,因此是异常。
如果你改为写:
var c = new Container();
c = c.WithWebApi(config);
它本质上是第一种语法的更详细的示例,并且会使用新功能正确更新 c
。
这看起来不错,应该可以。显然,从 ccontainer.WithWebApi(config);
返回的容器不是原始容器,这在这一点上是出乎意料的,因此至少有代码味道,因为它会引起可能的错误。最好编写几个单元测试并在 DryIoC Bitbucket site 上提出一个问题。
为了帮助您,这里有一个如何编写此类测试的模板。创建一个新的测试项目并执行以下操作:
安装 NuGet 包
Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
Install-Package DryIoc.WebApi.Owin.dll
Install-Package Xunit
控制器
public sealed class ValuesController : ApiController
{
private IWidgetService WidgetService { get; }
public ValuesController(IWidgetService widgetService)
{
if(widgetService == null)
throw new ArgumentNullException(nameof(widgetService));
WidgetService = widgetService;
}
[HttpGet]
public IEnumerable<Widget> Get()
{
return WidgetService.GetWidgets().ToArray();
}
}
OWIN 启动
public sealed class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration()
.ConfigureRouting()
.ConfigureDependencyInjection();
app.UseWebApi(config);
// protip: use OWIN error page instead of ASP.NET yellow pages for better diagnostics
// Install-Package Microsoft.Owin.Diagnostics
// app.UseErrorPage(ErrorPageOptions.ShowAll);
}
private static HttpConfiguration ConfigureRouting(this HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "default",
routeTemplate: "/",
defaults: new { controller = "values" });
return config;
}
private static HttpConfiguration ConfigureDependencyInjection(this HttpConfiguration config)
{
new Container()
.Register<IWidgetService, WidgetService>(Reuse.Singleton)
.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton)
.WithWebApi(config);
return config;
}
}
单元测试
[Fact]
public async Task ShouldFoo()
{
const string baseAddress = "http://localhost:8000";
using (WebApp.Start<Startup>(baseAddress))
{
var httpclient = new HttpClient
{
BaseAddress = new Uri(baseAddress)
};
var response = await httpclient.GetAsync("/");
Assert.That(...);
}
}