Skip to content

Commit

Permalink
Added ApplicationShutdownMethod support + cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
davidebbo committed Mar 1, 2011
1 parent c1b8c86 commit 586b23d
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 77 deletions.
25 changes: 25 additions & 0 deletions TestLibrary/MyStartupCode.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,53 @@
using System.Web.Mvc;
using System.Web.Routing;
using System;

[assembly: WebActivator.PreApplicationStartMethod(typeof(TestLibrary.MyStartupCode), "Start")]
[assembly: WebActivator.PreApplicationStartMethod(typeof(TestLibrary.MyStartupCode), "Start2")]
[assembly: WebActivator.PostApplicationStartMethod(typeof(TestLibrary.MyStartupCode), "CallMeAfterAppStart")]
[assembly: WebActivator.ApplicationShutdownMethod(typeof(TestLibrary.MyStartupCode), "CallMeWhenAppEnds")]

namespace TestLibrary {
public static class MyStartupCode {
public static bool StartCalled { get; set; }
public static bool Start2Called { get; set; }
public static bool CallMeAfterAppStartCalled { get; set; }
public static bool CallMeWhenAppEndsCalled { get; set; }

internal static void Start() {
if (StartCalled) {
throw new Exception("Unexpected second call to Start");
}

StartCalled = true;
}

public static void Start2() {
if (Start2Called) {
throw new Exception("Unexpected second call to Start2");
}

Start2Called = true;
}

public static void CallMeAfterAppStart() {
// This gets called after global.asax's Application_Start

if (CallMeAfterAppStartCalled) {
throw new Exception("Unexpected second call to CallMeAfterAppStart");
}

CallMeAfterAppStartCalled = true;
}

public static void CallMeWhenAppEnds() {
// This gets called when the app shuts down

if (CallMeWhenAppEndsCalled) {
throw new Exception("Unexpected second call to CallMeWhenAppEnds");
}

CallMeWhenAppEndsCalled = true;
}
}
}
18 changes: 16 additions & 2 deletions TestWebApp/App_Code/AppCodeStartupCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,24 @@
using System.Web;

[assembly: WebActivator.PostApplicationStartMethod(typeof(AppCodeStartupCode), "Start")]
[assembly: WebActivator.ApplicationShutdownMethodAttribute(typeof(AppCodeStartupCode), "Shutdown")]

public class AppCodeStartupCode {
public static bool Called { get; set; }
public static bool StartCalled { get; set; }
public static void Start() {
Called = true;
if (StartCalled) {
throw new Exception("Unexpected second call to Start");
}

StartCalled = true;
}

public static bool ShutdownCalled { get; set; }
public static void Shutdown() {
if (ShutdownCalled) {
throw new Exception("Unexpected second call to Shutdown");
}

ShutdownCalled = true;
}
}
1 change: 1 addition & 0 deletions TestWebApp/TestWebApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<AssemblyName>TestWebApp</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<MvcBuildViews>false</MvcBuildViews>
<UseIISExpress>false</UseIISExpress>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand Down
2 changes: 1 addition & 1 deletion TestWebApp/Views/Home/Index.aspx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<%
if (!TestLibrary.MyStartupCode.StartCalled || !TestLibrary.MyStartupCode.Start2Called ||
!TestLibrary.MyStartupCode.CallMeAfterAppStartCalled || !TestWebApp.TestStartupCode.MyStartupCode.StartCalled ||
!AppCodeStartupCode.Called) {
!AppCodeStartupCode.StartCalled || TestLibrary.MyStartupCode.CallMeWhenAppEndsCalled) {
throw new Exception("Startup methods were not correctly called");
}
%>
Expand Down
97 changes: 47 additions & 50 deletions WebActivator/ActivationManager.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Compilation;
using System.Web.Hosting;
using System;

namespace WebActivator {
public class ActivationManager {
private static bool hasInited;
private static bool _hasInited;
private static List<Assembly> _assemblies;

public static void Run() {
if (!hasInited) {
if (!_hasInited) {
RunPreStartMethods();

// Register our module to handle any Post Start methods. But outside of ASP.NET, just run them now
Expand All @@ -24,7 +24,7 @@ public static void Run() {
RunPostStartMethods();
}

hasInited = true;
_hasInited = true;
}
}

Expand All @@ -47,73 +47,70 @@ private static IEnumerable<Assembly> Assemblies {
}
}

public static void RunPreStartMethods() {
// Go through all the relevant assemblies and run the PreStart logic
foreach (var assembly in Assemblies) {
foreach (PreApplicationStartMethodAttribute preStartAttrib in assembly.GetPreAppStartAttributes()) {
// Invoke the method that the attribute points to
preStartAttrib.InvokeMethod();
private static IEnumerable<string> GetAssemblyFiles() {
// When running under ASP.NET, find assemblies in the bin folder.
// Outside of ASP.NET, use whatever folder WebActivator itself is in
string directory = HostingEnvironment.IsHosted
? HttpRuntime.BinDirectory
: Path.GetDirectoryName(typeof(ActivationManager).Assembly.Location);
return Directory.GetFiles(directory, "*.dll");
}

// Return all the App_Code assemblies
private static IEnumerable<Assembly> AppCodeAssemblies {
get {
// Return an empty list if we;re not hosted or there aren't any
if (!HostingEnvironment.IsHosted || !_hasInited || BuildManager.CodeAssemblies == null) {
return Enumerable.Empty<Assembly>();
}

return BuildManager.CodeAssemblies.OfType<Assembly>();
}
}

public static void RunPreStartMethods() {
RunActivationMethods<PreApplicationStartMethodAttribute>();
}

public static void RunPostStartMethods() {
// Go through all the relevant assemblies and run the PostStart logic
foreach (var assembly in Assemblies) {
foreach (PostApplicationStartMethodAttribute postStartAttrib in assembly.GetPostAppStartAttributes()) {
// Invoke the method that the attribute points to
postStartAttrib.InvokeMethod();
}
}
RunActivationMethods<PostApplicationStartMethodAttribute>();
}

private static void ProcessAppCodeAssemblies() {
if (BuildManager.CodeAssemblies != null) {
// Go through all the App_Code assemblies
foreach (var assembly in BuildManager.CodeAssemblies.OfType<Assembly>()) {
// Fail if there are any PreStart attribs in App_Code as we can't call them since App_Code is not even compiled before App_Start.
foreach (PreApplicationStartMethodAttribute preStartAttrib in assembly.GetPreAppStartAttributes()) {
throw new Exception(String.Format(
"PreApplicationStartMethodAttribute cannot be used in AppCode (for method {0}.{1}). Please use PostApplicationStartMethodAttribute instead.",
preStartAttrib.Type.FullName, preStartAttrib.MethodName));
}
public static void RunShutdownMethods() {
RunActivationMethods<ApplicationShutdownMethodAttribute>();
}

foreach (PostApplicationStartMethodAttribute postStartAttrib in assembly.GetPostAppStartAttributes()) {
postStartAttrib.InvokeMethod();
}
// Call the relevant activation method from all assemblies
private static void RunActivationMethods<T>() where T : BaseActivationMethodAttribute {
foreach (var assembly in Assemblies.Concat(AppCodeAssemblies)) {
foreach (BaseActivationMethodAttribute activationAttrib in assembly.GetActivationAttributes<T>()) {
activationAttrib.InvokeMethod();
}
}
}

private static IEnumerable<string> GetAssemblyFiles() {
// When running under ASP.NET, find assemblies in the bin folder.
// Outside of ASP.NET, use whatever folder WebActivator itself is in
string directory = HostingEnvironment.IsHosted
? HttpRuntime.BinDirectory
: Path.GetDirectoryName(typeof(ActivationManager).Assembly.Location);
return Directory.GetFiles(directory, "*.dll");
}

class StartMethodCallingModule : IHttpModule {
private static object initLock = new object();
private static bool hasInited;
private static object _lock = new object();
private static int _initializedModuleCount;

public void Init(HttpApplication context) {

// Make sure we only call the methods once per app domain
lock (initLock) {
if (!hasInited) {
lock (_lock) {
// Keep track of the number of modules initialized and
// make sure we only call the post start methods once per app domain
if (_initializedModuleCount++ == 0) {
RunPostStartMethods();

// Process any attribute found in App_Code.
ProcessAppCodeAssemblies();

hasInited = true;
}
}
}

public void Dispose() {
lock (_lock) {
// Call the shutdown methods when the last module is disposed
if (--_initializedModuleCount == 0) {
RunShutdownMethods();
}
}
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions WebActivator/ApplicationShutdownMethodAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace WebActivator {
// Same as PreApplicationStartMethodAttribute, but for methods to be called when the app shuts down
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class ApplicationShutdownMethodAttribute : BaseActivationMethodAttribute {
public ApplicationShutdownMethodAttribute(Type type, string methodName)
: base(type, methodName) {
}
}
}
20 changes: 5 additions & 15 deletions WebActivator/AssemblyExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace WebActivator {
static class AssemblyExtensions {
public static IEnumerable<PreApplicationStartMethodAttribute> GetPreAppStartAttributes(this Assembly assembly) {
// Go through all the PreApplicationStartMethodAttribute attributes
// Note that this is *our* attribute, not the System.Web namesake

return assembly.GetCustomAttributes(
typeof(PreApplicationStartMethodAttribute),
inherit: false).OfType<PreApplicationStartMethodAttribute>();
}

public static IEnumerable<PostApplicationStartMethodAttribute> GetPostAppStartAttributes(this Assembly assembly) {
// Return all the attributes of a given type from an assembly
public static IEnumerable<T> GetActivationAttributes<T>(this Assembly assembly) where T : BaseActivationMethodAttribute {
return assembly.GetCustomAttributes(
typeof(PostApplicationStartMethodAttribute),
inherit: false).OfType<PostApplicationStartMethodAttribute>();
typeof(T),
inherit: false).OfType<T>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
using System.Reflection;

namespace WebActivator {
// This attribute is similar to its System.Web namesake, except that
// it can be used multiple times on an assembly.
// Base class of all the activation attributes
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public abstract class BaseApplicationStartMethodAttribute : Attribute {
public abstract class BaseActivationMethodAttribute : Attribute {
private Type _type;
private string _methodName;

public BaseApplicationStartMethodAttribute(Type type, string methodName) {
public BaseActivationMethodAttribute(Type type, string methodName) {
_type = type;
_methodName = methodName;
}
Expand Down
3 changes: 1 addition & 2 deletions WebActivator/PostApplicationStartMethodAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
using System;
using System.Reflection;

namespace WebActivator {
// Same as PreApplicationStartMethodAttribute, but for methods to be called after App_Start
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class PostApplicationStartMethodAttribute : BaseApplicationStartMethodAttribute {
public sealed class PostApplicationStartMethodAttribute : BaseActivationMethodAttribute {
public PostApplicationStartMethodAttribute(Type type, string methodName)
: base(type, methodName) {
}
Expand Down
3 changes: 1 addition & 2 deletions WebActivator/PreApplicationStartMethodAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using System;
using System.Reflection;

namespace WebActivator {
// This attribute is similar to its System.Web namesake, except that
// it can be used multiple times on an assembly.
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class PreApplicationStartMethodAttribute : BaseApplicationStartMethodAttribute {
public sealed class PreApplicationStartMethodAttribute : BaseActivationMethodAttribute {
public PreApplicationStartMethodAttribute(Type type, string methodName)
: base(type, methodName) {
}
Expand Down
3 changes: 2 additions & 1 deletion WebActivator/WebActivator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@
<ItemGroup>
<Compile Include="ActivationManager.cs" />
<Compile Include="AssemblyExtensions.cs" />
<Compile Include="ApplicationShutdownMethodAttribute.cs" />
<Compile Include="PostApplicationStartMethodAttribute.cs" />
<Compile Include="PreApplicationStartMethodAttribute.cs" />
<Compile Include="BaseApplicationStartMethodAttribute.cs" />
<Compile Include="BaseActivationMethodAttribute.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
9 changes: 9 additions & 0 deletions WebActivatorTest/WebActivatorUnitTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,14 @@ public void TestWebActivatorPostStartMethodsGetCalled() {
Assert.IsFalse(TestLibrary.MyStartupCode.Start2Called);
Assert.IsTrue(TestLibrary.MyStartupCode.CallMeAfterAppStartCalled);
}

[TestMethod]
public void TestWebActivatorShutdownMethodsGetCalled() {
MyStartupCode.CallMeWhenAppEndsCalled = false;

WebActivator.ActivationManager.RunShutdownMethods();

Assert.IsTrue(MyStartupCode.CallMeWhenAppEndsCalled);
}
}
}

0 comments on commit 586b23d

Please sign in to comment.