3

我正在探索 DryIoc 在 .NET WebAPI 应用程序中的使用,并注意到初始化步骤有一个奇怪的行为。在一个简单的测试 webapi 应用程序中,我有以下 DryIoc 注册类,它在 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# 细微差别?

4

2 回答 2

3

This is because .WithWebApi() is an extension method per the 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;
    }

In the first syntax example, you create a new instance of Container and pass that newly created instance to .WithWebApi(). This in turn updates the container instance and finally returns it back to variable c.

In the second syntax example, you are never returning the value of the extension method BACK to the original variable, updating it. You are calling it as if it were a void method, which does nothing in this case, hence the exception.

If you had instead written:

var c = new Container();
c = c.WithWebApi(config);

it would have essentially been a more verbose example of the first syntax and would have properly updated c with the new functionality.

于 2016-04-27T20:48:13.057 回答
1

这似乎很好,应该可以工作。显然,从返回的容器ccontainer.WithWebApi(config);不是原始容器,这在这一点上是出乎意料的,因此至少是代码味道,因为它会引起可能的错误。最好编写几个单元测试并在DryIoC Bitbucket 站点上打开一个问题。

为了帮助您,这里有一个如何编写此类测试的模板。创建一个新的测试项目并执行以下操作:


安装 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();
    }
}

欧文启动

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(...);
    }
}
于 2016-04-27T20:54:48.690 回答