Spring.NET and MVC3 on IIS7 - session scope behaviour(转http://stackoverflow.com/questions/6060386/spring-net-and-mvc3-on-iis7-session-scope-behaviour)

Coordinator
Nov 22, 2011 at 2:03 PM

It's a bug.

After some excessive debugging of the spring.net source code we found out:

  1. Support for session scoped objects is given by the spring.web dll
  2. The spring.web.mvc dll does not depend on spring.web
  3. This means, it is impossible to instantiate an MvcApplicationContext that can resolve session scoped objects

The following solution shows a custom MvcApplicationContext that fully enables session scoped objects within MVC3 using spring.net.

The reason why a standard application context cannot resolve web scopes is that it's using the class RootObjectDefinition whis doesn't know about the scope attribute (in the config xml). The WebApplicationContext instead instantiates RootWebObjectDefinition types, which know the scope.

The WebObjectsFactory overrides the method CreateRootObjectDefinition which returns instances of RootWebObjectDefinition. This is the one, we want to return from our application context. This is done by overriding the method CreateObjectsFactory.

Next thing, we have to override is the method CreateXmlObjectDefinitionReader. When spring is reading the metadata from the config, it will not parse additional attributes like scope if we don't take a specific reader. Therefore we will use the WebObjectDefinitionReader in our application context.

For the configuration of your session scoped objects, you can either leave out the singleton attribute or set it explicitly to true. Otherwise with value false the session scope will be disabled for sure.

EXAMPLE:

<objects xmlns="http://www.springframework.net">
    <object id="shoppingCart" type="ShoppingCart, ..." singleton="true" scope="session" />
</objects>

Step-by-Step solution:

  1. Create MvcWebApplicationContext inheriting from MvcApplicationContext. You will need to override the two methods mentioned above and create default constructors.
  2. Create a MvcWebContextHandler inheriting from MvcContextHandler. This will trigger that our custom application context will be used.
  3. Use custom context handler in your web.config.
  4. For IIS6 support or visual studio build-in webserver: add WebSupportModule to system.web section.
  5. For IIS7 support: Add WebSupportModule to system.webserver section.

web.config:

<configSections>
    <sectionGroup name="spring">
        <section name="context" type="YourNamspace.MvcWebContextHandler, YourAssembly"/>    
        ....
    </sectionGroup>    
    ....
</configSections>

<!-- IIS6 -->
<system.web>
    <httpModules>
        <add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web"/>
    </httpModules>
</system.web>

<!-- IIS7 -->
<system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules runAllManagedModulesForAllRequests="true" >
        <add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web"/>
    </modules>
</system.webServer>

Custom application context class:

public class MvcWebApplicationContext: MvcApplicationContext {

    public MvcWebApplicationContext(string name, bool caseSensitive, params string[] configurationLocations)
    : this(new MvcApplicationContextArgs(name, null, configurationLocations, null, caseSensitive))
    { }

    public MvcWebApplicationContext(string name, bool caseSensitive, IApplicationContext parentContext, params string[] configurationLocations)
    : this(new MvcApplicationContextArgs(name, parentContext, configurationLocations, null, caseSensitive))
    { }

    public MvcWebApplicationContext(MvcApplicationContextArgs args)
    : base(args)
    { }

    public MvcWebApplicationContext(string name, bool caseSensitive, string[] configurationLocations, IResource[] configurationResources)
    : this(new MvcApplicationContextArgs(name, null, configurationLocations, configurationResources, caseSensitive))
    { }

    public MvcWebApplicationContext(params string[] configurationLocations)
    : this(new MvcApplicationContextArgs(string.Empty, null, configurationLocations, null, false))
    { }

    protected override XmlObjectDefinitionReader CreateXmlObjectDefinitionReader(DefaultListableObjectFactory objectFactory)
    {
        return new WebObjectDefinitionReader(GetContextPathWithTrailingSlash(), objectFactory, new XmlUrlResolver());
    }

    protected override DefaultListableObjectFactory CreateObjectFactory()
    {
        return new WebObjectFactory(GetContextPathWithTrailingSlash(), IsCaseSensitive);
    }

    private string GetContextPathWithTrailingSlash()
    {
        string contextPath = this.Name;
        if (contextPath == DefaultRootContextName)
        {
            contextPath = "/";
        }
        else 
        {
            contextPath = contextPath + "/";
        }
        return contextPath;
    }
}

Custom context handler class:

public class MvcWebContextHandler : MvcContextHandler {

    protected override Type DefaultApplicationContextType
    {
        get { return typeof(MvcWebApplicationContext); }
    }  
}

We added this bug to Spring.NET's issue tracker: https://jira.springsource.org/browse/SPRNET-1450