SPRNET-1510 change SpringWebApiDependencyResolver to properly accept sep. config sources for child/nested contexts

This commit is contained in:
Steve Bohlen
2012-07-09 16:07:36 -04:00
parent d8ff77d999
commit 1b6b0a92dc
18 changed files with 359 additions and 27 deletions

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net">
<object type="Spring.Mvc4QuickStart.Controllers.SuffixNestedController, Spring.Mvc4QuickStart" singleton="false" >
<property name="Suffix" value="***This message comes from a child Spring.NET context!***" />
</object>
</objects>

View File

@@ -1,17 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net">
<objects xmlns="http://www.springframework.net" default-autowire="constructor">
<object type="Spring.Mvc4QuickStart.Controllers.HomeController, Spring.Mvc4QuickStart" singleton="false" >
<property name="Message" value="Welcome to ASP.NET MVC4 powered by Spring.NET!" />
</object>
<object type="Spring.Mvc4QuickStart.Controllers.CountController, Spring.Mvc4QuickStart" singleton="false" />
<object type="Spring.Mvc4QuickStart.Controllers.SuffixController, Spring.Mvc4QuickStart" singleton="false" >
<property name="Suffix" value="_Spring.NET_was_Here!" />
</object>
<object type="Spring.Mvc4QuickStart.Controllers.OdataController, Spring.Mvc4QuickStart" singleton="false" />
<!--intentionally do NOT register the AccountController or the ValuesController with the container; demonstrates that the underlying
default controller factory will properly (attempt to!) resolve all controllers not registered with Spring.NET
using its default controller resolution behavoir-->

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net" default-autowire="constructor">
<!-- this obj is defined as a singleton and given an initial value for Count of 0
so it can be easily verified as being instantiated only once by the ApplicationContext
no matter how many child contexts are instatiated as part of WebAPI calls -->
<object type="Spring.Mvc4QuickStart.Counter, Spring.Mvc4QuickStart">
<property name="Count" value="0" />
</object>
</objects>

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Spring.Mvc4QuickStart.Controllers
{
public class CountController : Controller
{
private readonly Counter _counter;
public CountController(Counter counter)
{
_counter = counter;
}
public ActionResult Index()
{
ViewBag.Message = _counter.Count;
return View();
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Spring.Mvc4QuickStart.Controllers
[Queryable]
public IQueryable<SampleDataItem> Get()
{
var campaigns = new[]
var dataItems = new[]
{
new SampleDataItem { SomeProperty = 1 },
new SampleDataItem { SomeProperty = 2 },
@@ -25,7 +25,7 @@ namespace Spring.Mvc4QuickStart.Controllers
new SampleDataItem { SomeProperty = 4 },
};
return campaigns.AsQueryable();
return dataItems.AsQueryable();
}
// POST /api/odata

View File

@@ -7,29 +7,29 @@ namespace Spring.Mvc4QuickStart.Controllers
{
public string Suffix { get; set; }
// GET /api/values
// GET /api/suffix
public IEnumerable<string> Get()
{
return new string[] { string.Format("value1{0}", Suffix), string.Format("value2{0}", Suffix) };
return new string[] { string.Format("value1_{0}", Suffix), string.Format("value2_{0}", Suffix) };
}
// GET /api/values/5
// GET /api/suffix/5
public string Get(int id)
{
return string.Format("value{0}", Suffix);
return string.Format("value{0}_{1}", Suffix, id);
}
// POST /api/values
// POST /api/suffix
public void Post(string value)
{
}
// PUT /api/values/5
// PUT /api/suffix/5
public void Put(int id, string value)
{
}
// DELETE /api/values/5
// DELETE /api/suffix/5
public void Delete(int id)
{
}

View File

@@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Web.Http;
namespace Spring.Mvc4QuickStart.Controllers
{
public class SuffixNestedController : ApiController
{
//same public API as SuffixController, only here so that its config can be shown to
// be properly read out of a separate 'child' config file
public string Suffix { get; set; }
// GET /api/suffixnested
public IEnumerable<string> Get()
{
return new string[] { string.Format("value1_{0}", Suffix), string.Format("value2_{0}", Suffix) };
}
// GET /api/suffixnested/5
public string Get(int id)
{
return string.Format("value{0}_{1}", Suffix, id);
}
// POST /api/suffixnested
public void Post(string value)
{
}
// PUT /api/suffixnested/5
public void Put(int id, string value)
{
}
// DELETE /api/suffixnested/5
public void Delete(int id)
{
}
}
}

View File

@@ -0,0 +1,14 @@
namespace Spring.Mvc4QuickStart
{
//intended to be instantiated as a singleton by the parent ApplicationContext so that it can keep track of its hit-count
// as the page is visited (NOTE: this impl. is for demo purposes only and is obviously NOT threadsafe!)
public class Counter
{
private int _count;
public int Count
{
get { return _count++; }
set { _count = value; }
}
}
}

View File

@@ -23,5 +23,23 @@ namespace Spring.Mvc4QuickStart
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
protected override System.Web.Http.Dependencies.IDependencyResolver BuildWebApiDependencyResolver()
{
//get the 'default' resolver, populated from the 'main' config metadata
var resolver = base.BuildWebApiDependencyResolver();
//check if its castable to a SpringWebApiDependencyResolver
var springResolver = resolver as SpringWebApiDependencyResolver;
//if it is, add additional config sources as needed
if (springResolver != null)
{
springResolver.AddChildApplicationContextConfigurationLocation("file://~/Config/child_controllers.xml");
}
//return the fully-configured resolver
return resolver;
}
}
}

View File

@@ -121,12 +121,15 @@
<Compile Include="App_Start\FilterConfig.cs" />
<Compile Include="App_Start\RouteConfig.cs" />
<Compile Include="Controllers\AccountController.cs" />
<Compile Include="Controllers\CountController.cs" />
<Compile Include="Controllers\SuffixNestedController.cs" />
<Compile Include="Controllers\HomeController.cs" />
<Compile Include="Controllers\OdataController.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Controllers\ValuesController.cs" />
<Compile Include="Controllers\SuffixController.cs" />
<Compile Include="Counter.cs" />
<Compile Include="Global.asax.cs">
<DependentUpon>Global.asax</DependentUpon>
</Compile>
@@ -137,6 +140,12 @@
<Content Include="Config\controllers.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Config\child_controllers.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Config\objects.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Content\themes\base\images\ui-bg_flat_0_aaaaaa_40x100.png" />
<Content Include="Content\themes\base\images\ui-bg_flat_75_ffffff_40x100.png" />
<Content Include="Content\themes\base\images\ui-bg_glass_55_fbf9ee_1x400.png" />
@@ -228,6 +237,9 @@
<ItemGroup>
<Content Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Count\Index.cshtml" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

View File

@@ -0,0 +1,2 @@
<p>Singleton Counter object is keeping track of requests for this page.</p>
<h2>Current Count: @ViewBag.Message</h2>

View File

@@ -14,6 +14,7 @@
<spring>
<context>
<resource uri="file://~/Config/controllers.xml"/>
<resource uri="file://~/Config/objects.xml"/>
</context>
</spring>
<connectionStrings>

View File

@@ -60,7 +60,7 @@ namespace Spring.Web.Mvc
/// Defaults to using the root (default) Application Context.
/// </remarks>
/// <value>The name of the application context.</value>
public static string ApplicationContextName { get; set; }
public string ApplicationContextName { get; set; }
/// <summary>

View File

@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http.Dependencies;
using System.Web.Http.Services;
using Spring.Context;
using Spring.Context.Support;
using Spring.Core.IO;
namespace Spring.Web.Mvc
@@ -38,16 +40,28 @@ namespace Spring.Web.Mvc
/// <exception cref="NotImplementedException"></exception>
public virtual IDependencyScope BeginScope()
{
var abstractXmlApplicationContext = ApplicationContext as AbstractXmlApplicationContext;
if (abstractXmlApplicationContext != null)
if (HasApplicationContext && (HasChildApplicationContextConfigurationLocations || HasChildApplicationContextConfigurationResources))
{
var locations = abstractXmlApplicationContext.ConfigurationLocations;
var resources = abstractXmlApplicationContext.ConfigurationResources;
string[] configurationLocations = null;
if (HasChildApplicationContextConfigurationLocations)
{
configurationLocations = ChildApplicationContextConfigurationLocations.ToArray();
}
var args = new MvcApplicationContextArgs(string.Format("child_of_{0}", ApplicationContext.Name), ApplicationContext, locations, resources, false);
IResource[] configurationResources = null;
if (HasChildApplicationContextConfigurationResources)
{
configurationResources = ChildApplicationContextConfigurationResources.ToArray();
}
var childContextName = string.Format("child_of_{0}", ApplicationContext.Name);
var args = new MvcApplicationContextArgs(childContextName, ApplicationContext, configurationLocations, configurationResources, false);
var childContext = new MvcApplicationContext(args);
var newResolver = new SpringWebApiDependencyResolver(childContext) { ApplicationContextName = childContextName };
RegisterContextIfNeeded(childContext);
var newResolver = new SpringWebApiDependencyResolver(new MvcApplicationContext(args));
return newResolver;
}
else
@@ -55,5 +69,76 @@ namespace Spring.Web.Mvc
return this;
}
}
private void RegisterContextIfNeeded(IApplicationContext childContext)
{
if (!ContextRegistry.IsContextRegistered(childContext.Name))
{
ContextRegistry.RegisterContext(childContext);
}
}
private bool HasChildApplicationContextConfigurationResources
{
get
{
return ChildApplicationContextConfigurationResources != null &&
ChildApplicationContextConfigurationResources.Count > 0;
}
}
private bool HasChildApplicationContextConfigurationLocations
{
get
{
return ChildApplicationContextConfigurationLocations != null &&
ChildApplicationContextConfigurationLocations.Count > 0;
}
}
private bool HasApplicationContext
{
get { return ApplicationContext != null; }
}
/// <summary>
/// Gets or sets the child configuration locations.
/// </summary>
/// <value>The child configuration locations.</value>
public virtual IList<string> ChildApplicationContextConfigurationLocations { protected get; set; }
/// <summary>
/// Gets or sets the child configuration resources.
/// </summary>
/// <value>The child configuration resources.</value>
public virtual IList<IResource> ChildApplicationContextConfigurationResources { protected get; set; }
/// <summary>
/// Adds the child configuration resource.
/// </summary>
/// <param name="resource">The resource.</param>
public virtual void AddChildApplicationContextConfigurationResource(IResource resource)
{
if (null == ChildApplicationContextConfigurationResources)
{
ChildApplicationContextConfigurationResources = new List<IResource>();
}
ChildApplicationContextConfigurationResources.Add(resource);
}
/// <summary>
/// Adds the child configuration location.
/// </summary>
/// <param name="location">The location.</param>
public virtual void AddChildApplicationContextConfigurationLocation(string location)
{
if (null == ChildApplicationContextConfigurationLocations)
{
ChildApplicationContextConfigurationLocations = new List<string>();
}
ChildApplicationContextConfigurationLocations.Add(location);
}
}
}

View File

@@ -29,10 +29,6 @@ using Spring.Web.Mvc.Tests.Controllers;
namespace Spring.Web.Mvc.Tests
{
// internal class MockContext : HttpContextBase
// {
// }
[TestFixture]
public class SpringMvcDependencyResolverTests
{

View File

@@ -67,9 +67,7 @@
<Compile Include="..\Spring.Web.Mvc3.Tests\Controllers\SecondContainerRegisteredController.cs">
<Link>Controllers\SecondContainerRegisteredController.cs</Link>
</Compile>
<Compile Include="..\Spring.Web.Mvc3.Tests\SpringMvcDependencyResolverTests.cs">
<Link>SpringMvcDependencyResolverTests.cs</Link>
</Compile>
<Compile Include="SpringMvcDependencyResolverTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Spring\Spring.Core\Spring.Core.2010.csproj">
@@ -78,7 +76,7 @@
<Private>True</Private>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Spring\Spring.Web.Mvc4\Spring.Web.Mvc4.2010.csproj">
<Project>{904C97E0-3667-4D12-A55F-6CC2E6F68A0A}</Project>
<Project>{CB351EB2-049D-40BE-BB09-1CE4ED68FE67}</Project>
<Name>Spring.Web.Mvc4.2010</Name>
</ProjectReference>
</ItemGroup>

View File

@@ -25,6 +25,8 @@
<include name="**/*.cs" />
<include name="../CommonAssemblyInfo.cs" />
<include name="../Spring.Web.Mvc3.Tests/**/*.cs" />
<include name="../Spring.Web.Mvc4.Tests/**/*.cs" />
<exclude name="../Spring.Web.Mvc3.Tests/**/SpringMvcDependencyResolverTests.cs" />
</sources>
<resources prefix="Spring" dynamicprefix="true" failonempty="true">
<include name="../Spring.Web.Mvc3.Tests/**/*.resx" />

View File

@@ -0,0 +1,117 @@
#region License
/*
* Copyright © 2002-2011 the original author or authors.
*
* 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
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using NUnit.Framework;
using Spring.Context.Support;
using Spring.Web.Mvc.Tests.Controllers;
namespace Spring.Web.Mvc.Tests
{
[TestFixture]
public class SpringMvcDependencyResolverTests
{
#region Setup/Teardown
[SetUp]
public void _TestSetup()
{
ContextRegistry.Clear();
_context = new MvcApplicationContext("file://objectsMvc.xml");
_mvcNamedContext = new MvcApplicationContext("named", false, "file://namedContextObjectsMvc.xml");
ContextRegistry.RegisterContext(_context);
ContextRegistry.RegisterContext(_mvcNamedContext);
_resolver = new SpringMvcDependencyResolver(_context);
_resolver.ApplicationContextName = string.Empty;
}
#endregion
private MvcApplicationContext _context;
private MvcApplicationContext _mvcNamedContext;
private SpringMvcDependencyResolver _resolver;
[Test]
public void CanUseNamedContextToResolveType()
{
_resolver.ApplicationContextName = "named";
var service = _resolver.GetService(typeof (NamedContextController));
Assert.NotNull(service);
}
[Test]
public void CanUseUnnamedContextToResolveType()
{
Assume.That(_resolver.ApplicationContextName, Is.Empty, "Resolver should not have a named-context set for this test!");
var service = _resolver.GetService<FirstContainerRegisteredController>();
Assert.NotNull(service);
}
[Test]
public void RequestForNullTypeReturnsNull()
{
var service = _resolver.GetService(null);
Assert.That(service, Is.Null);
}
[Test]
public void CanReturnCollectionOfTypes()
{
var services = _resolver.GetServices<FirstContainerRegisteredController>();
Assert.That(services.Count(), Is.EqualTo(2));
foreach (var service in services)
{
Assert.That(service, Is.TypeOf(typeof(FirstContainerRegisteredController)), "Service Returned was not of the expected Type.");
}
}
[Test]
public void RequestForSingleInstanceOfMultiplyRegisteredTypeReturnsFirstDefinition()
{
var service = _resolver.GetService<FirstContainerRegisteredController>();
Assert.That(service.TestValue, Is.EqualTo("First Definition of Type"));
}
[Test]
public void RequestForSingleUndefinedTypeReturnsNull()
{
var service = _resolver.GetService<NotInContainerController>();
Assert.That(service, Is.Null);
}
[Test]
public void RequestForCollectionOfUndefinedTypesReturnsEmptyCollection()
{
var services = _resolver.GetServices<NotInContainerController>();
Assert.That(services, Is.Empty);
}
}
}