CHAPTER 3

image

The ASP.NET Life Cycles

The ASP.NET platform defines two important life cycles that underpin the MVC framework. The first is the application life cycle, which tracks the life of a web application from the moment it starts to the moment it is terminated. The second is the request life cycle, which defines the path that an HTTP request follows as it moves through the ASP.NET platform from the point at which the initial request is received until the response is sent. In this chapter, I describe both life cycles and the context objects that the ASP.NET platform uses to describe them and the overall state of the application. Table 3-1 summarizes the chapter.

Table 3-1. Chapter Summary

Problem

Solution

Listing

Perform actions when the application starts and stops.

Use the special Application_Start and Application_End methods in the global application class.

13

Monitor or modify a request as it is processed.

Handle the events that define the request life cycle.

48

Get information about the state of the application, the current request, or the response associated with it.

Use the properties defined by the context objects.

911, 13

Preparing the Example Project

For this chapter, I am going to continue use the SimpleApp project I created in Chapter 2 and that allows the user to vote for a color. You will find that most of the MVC framework applications that I build in this book are pretty simple because my focus is on the ASP.NET platform.

image Tip  Don’t forget that you can download the source code for all the examples in this book for free from www.apress.com.

The ASP.NET Application Life Cycle

As an MVC framework developer, you are used to starting an application and letting it handle requests, using Visual Studio during development or on a production platform after deployment. At some later time, the application is stopped—perhaps for maintenance or an upgrade—and requests are no longer processed.

These two moments—the point at which an application starts and stops receiving requests—define the application life cycle, the management of which provides the most fundamental features for ASP.NET web applications. ASP.NET provides notifications when the application starts and when it is stopped in a controlled way, and these notifications are the topic of this section of this chapter. Table 3-2 puts the application life-cycle notifications in context.

Table 3-2. Putting the Application Life-Cycle Notification in Context

Question

Answer

What is it?

The application life-cycle notifications allow you to perform actions when the application starts and when it is shut down in a controlled way.

Why should I care?

The notifications are useful if you have one-off configuration tasks or if you need to release resources when the application is stopped. The most common use of the application life cycle by MVC framework developers is to configure a dependency injection container.

How is it used by the MVC framework?

The MVC framework uses the application life cycle to perform configuration tasks that affect all requests, such as setting up routes, areas, and content bundles.

Understanding the Application Life Cycle

The life cycle of an ASP.NET application begins the moment the application is started and continues while HTTP requests are received from clients and processed to generate responses. It includes matching requests to controllers and actions and rendering content from Razor views. The life cycle ends when the application is stopped.

The ASP.NET platform provides notifications of the two stages of the life cycle through methods defined by the global application class. The global application class has been around since the earliest versions of ASP.NET and consists of two files: Global.asax and Global.asax.cs.

Strictly speaking, the Global.asax file is the global application class, and the Global.asax.cs file is the associated code-behind file. This is the classic Web Forms approach to splitting declarative and programmatic code and is an artifact of the origins of ASP.NET. The Global.asax file used to be more important in ASP.NET applications, but it is just there for compatibility these days, even in Web Forms projects. Listing 3-1 shows the content of the Global.asax file from the example project, although you will never need to edit this file for an MVC framework project.

Listing 3-1.  The Contents of the Global.asax File

<%@ Application Codebehind="Global.asax.cs" Inherits="SimpleApp.MvcApplication" Language="C#" %>

image Tip  To see the contents of this file, right-click Global.asax in the Solution Explorer and select View Markup from the pop-up menu.

This is an example of a Web Forms directive that tells ASP.NET that the associated code file is called Global.asax.cs and that it is written in C#. (Remember that ASP.NET applications can be written in any .NET language and that Visual Basic is still widely used.)

The Global.asax file may be a mildly interesting artifact left over from earlier versions of ASP.NET, but for this chapter it is the Global.asax.cs file that is important. The role of these files has gradually changed as ASP.NET has matured, and now the term global application class is usually used to refer to the Global.asax.cs file to the extent that when you double-click Global.asax in the Solution Explorer, it is the Global.asax.cs file that opens in the editor.

Listing 3-2 shows the contents of the Global.asax.cs file that Visual Studio created for the example project.

Listing 3-2.  The Contents of the Global.asax.cs File

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
 
namespace SimpleApp {
    public class MvcApplication : System.Web.HttpApplication {
        protected void Application_Start() {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
    }
}

The default global application class is called MvcApplication and is derived from the System.Web.HttpApplication class. You’ll see many more classes from the System.Web namespace in this book because it is the home of the bulk of the ASP.NET platform functionality.

The MvcApplication class is instantiated by the ASP.NET framework, and the methods it defines are called at key moments in the application life cycle (and, as you’ll learn, in the request life cycle).

The default implementation of the MvcApplication class contains only one method, called Application_Start, but there is another method available, and I describe them both in the following sections.

Receiving Notifications When the Application Starts and Ends

The global application class supports two special methods that define the start and end of the application life cycle, as described by Table 3-3.

Table 3-3. The Global Application Class for Application Life-Cycle Notifications

Name

Description

Application_Start()

Called when the application is started

Application_End()

Called when the application is about to be terminated

The Application_Start method is called when the application is first started and provides an opportunity to perform one-off configuration tasks that affect the entire application. In Listing 3-2, you can see that Visual Studio has added statements to the Application_Start method that set up MVC areas and URL routes. When I created the example project in Chapter 2, I selected the option for the minimal initial project content, but additional statements would have been added to the Application_Start method if I had selected one of the other template options.

The Application_End method is called just before the application is terminated and is an opportunity to release any resources that the application maintains and to generally tidy up. The usual reason given for using this method is to release persistent database connections, but most modern web applications don’t manage connections directly, and I rarely find it necessary to implement the Application_End method in my own projects. Databases are pretty good at managing their own connections these days, which is good because the Application_End method is called only when the application is shut down in an orderly manner. You can’t rely on the method being called if the server fails or there is some other kind of sudden, unexpected problem such as a power outage.

I refer to these methods as being special because they are implemented in an odd way. These methods are not defined by the base for the global application class (which is System.Web.HttpApplication), so you don’t have to use the override keyword to implement them. In fact, the ASP.NET framework uses reflection to look for the methods by name. You won’t receive an error if you mistype the method names; ASP.NET just assumes you don’t want to be notified when the application starts or stops. For this reason, it is important to ensure that you test that your code is being called, which I demonstrate later in this chapter.

image Tip  You will sometimes see the Application_Start and Application_End methods defined with object Object and EventArgs arguments, following the convention for a C# event handler method. This is optional, and the ASP.NET framework is able to locate and call these methods with or without these arguments.

Calls to the Application_Start and Application_End methods bookend the life cycle of the application, and between those method calls ASP.NET receives and processes requests, as described in Figure 3-1. I’ll return to this diagram as I detail the request life cycle and show you how everything fits together.

9781430265412_Fig03-01.jpg

Figure 3-1. The application life cycle

Testing the Start and Stop Notifications

You can use the Application_Start and Application_End methods directly in your applications to perform one-off startup and shutdown activities, just as the MVC framework does. In this section, I’ll show you how to use the debugger to ensure that the methods are being called—something that is important when your custom code doesn’t work the way you expect.

The simplest way to check that these methods are being called is to use the Visual Studio debugger. In Listing 3-3 you can see how I have added calls to the static System.Diagnostics.Debugger.Break method to the Application_Start and Application_End methods, which has the same effect as setting a breakpoint using the Visual Studio code editor, but with the benefit of ensuring that you will get the same result I describe here if you re-create this example.

Listing 3-3.  Breaking the Debugger in the Global.asax.cs File

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
 
namespace SimpleApp {
    public class MvcApplication : System.Web.HttpApplication {
 
        protected void Application_Start() {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            System.Diagnostics.Debugger.Break();
        }
 
        protected void Application_End() {
            System.Diagnostics.Debugger.Break();
        }
    }
}

I have added a call to the Break method in the existing Application_Start method and added the Application_End method (which contains only a call to Break since my application doesn’t have any cleanup code and none is required by the MVC framework).

Testing the Start Notification

Testing the Application_Start method is simple. Select Start Debugging from the Visual Studio Debug menu, and Visual Studio will open a browser window. The ASP.NET framework begins initializing the application, creates an instance of the global application class, discovers the Application_Start method, and invokes it, which leads to the Break method being called. The execution of the application is halted, and control is passed to the debugger, as shown in Figure 3-2.

9781430265412_Fig03-02.jpg

Figure 3-2. Control is passed to the debugger when the Application_Start method is called

Select Continue from the Visual Studio Debug menu, and execution of the application will resume. The request from the browser is processed, generating an HTML response that allows the user to vote for a color.

Testing the Stop Notification

Testing the Application_End method is a little trickier because selecting Stop Debugging detaches the debugger from the application before the Application_End method is called. The browser window is closed, the debugger is terminated, and Visual Studio returns to its default state—but the Debugger.Break method isn’t invoked.

To test the code in the Application_End method, you must work with IIS Express directly, rather than through Visual Studio. IIS Express is a cut-down version of the IIS application server included with Visual Studio and is used to run ASP.NET applications during development.

Execution of the application will continue, and the time and day of the week will be displayed by the browser.

Locate the IIS Express icon on the Windows taskbar and right-click to make the pop-up menu appear. You will see a menu item for the SimpleApp project, and when you select it, you will see a Stop Site menu item, as shown in Figure 3-3.

9781430265412_Fig03-03.jpg

Figure 3-3. Stopping an application using IIS Express

When you select Stop Site, IIS Express will stop the application, and as part of this process, the Application_End method will be called. For my example, this means the Debugger.Break call is executed, as shown in Figure 3-4.

9781430265412_Fig03-04.jpg

Figure 3-4. Testing the code in the Application_End method

The ASP.NET Request Life Cycle

The global application class is also used to track the life cycle of individual requests, allowing you to follow each request as it passes through the ASP.NET platform into the MVC framework. The ASP.NET framework creates instances of the MvcApplication class defined in the Global.asax.cs file and uses the events it defines to shepherd the request through until the point where the response is generated and sent back to the browser. These events are not just for use by the application developer; they are used by the ASP.NET framework and the MVC framework to perform request handling (this will become clearer as I dig deeper into the detail). Table 3-4 puts the request life cycle in context.

Table 3-4. Putting the Request Life Cycle in Context

Question

Answer

What is it?

The request life cycle is described by a series of events that describe the progress of a request from when it is received through until the response is sent.

Why should I care?

The events are used when creating your own modules and handlers (which I introduce later in this chapter and describe properly in Chapters 4 and 5). You can also use these events to debug complex problems caused by interactions between ASP.NET components.

How is it used by the MVC framework?

The MVC framework includes a module and a handler. The module blocks requests for view files, and the handler is the component that locates the controller and action method that will process the request and renders the view that the action method selects.

Understanding the Request Life Cycle

The Application_Start and Application_End methods are not called on the MvcApplication instances that ASP.NET creates to handle requests. Instead, the ASP.NET framework triggers the sequence of events described in Table 3-5. These events describe the request life cycle.

Table 3-5. The Request Life Cycle Events Defined by the Global Application Class

Name

Description

BeginRequest

This is triggered as the first event when a new request is received.

AuthenticateRequest
PostAuthenticateRequest

The AuthenticateRequest event is triggered to identify the user who has made the request. When all of the event handlers have been processed, PostAuthenticateRequest is triggered.

AuthorizeRequest

AuthorizeRequest is triggered to authorize the request. When all of the event handlers have been processed, PostAuthorizeRequest is triggered.

ResolveRequestCache
PostResolveRequestCache

ResolveRequestCache is triggered to resolve the request from cached data. I describe the caching features in Chapters 11 and 12. When the event handlers have been processed, PostResolveRequestCache is triggered.

MapRequestHandler
PostMapRequestHandler

MapRequestHandler is triggered when the ASP.NET framework wants to locate a handler for the request. I introduce handlers later in this chapter and cover them in depth in Chapter 5. The PostMapRequestHandler event is triggered once the handler has been selected.

AcquireRequestState
PostAcquireRequestState

AcquireRequestState is triggered to obtain the state data associated with the request (such as session state). When all of the event handlers are processed, PostAcquireRequestState is triggered. I explain the different kinds of state data in Chapter 10.

PreRequestHandlerExecute
PostRequestHandlerExecute

These events are triggered immediately before and immediately after the handler is asked to process the request.

ReleaseRequestState
PostReleaseRequestState

ReleaseRequestState is triggered when the state data associated with the request is no longer required for request processing. When the event handlers have been processed, the PostReleaseRequestState event is triggered.

UpdateRequestCache

This event is triggered so that modules responsible for caching can update their state. I introduce the role of modules later in this chapter and describe them in depth in Chapter 4. I describe the built-in support for caching in Chapters 11 and 12.

LogRequest
PostLogRequest

This event is triggered to provide an opportunity for details of the request to be logged. When all of the event handlers have been processed, PostLogRequest is triggered.

EndRequest

This event is triggered when the request has been processed and the response is ready to be sent to the browser.

PreSendRequestHeaders
PreSendRequestContent

This event is triggered just before the HTTP headers are sent to the browser.

This event is triggered after the headers have been sent but before the content is sent to the browser.

Error

This event is triggered when an error is encountered; this can happen at any point in the request process. See Chapter 6 for details about error handling.

THE LIFE OF A REQUEST LIFE CYCLE HTTPAPPLICATION OBJECT

The ASP.NET framework will create multiple instances of the MvcApplication class to process requests, and these instances can be reused so that they process several requests over their lifetime. The ASP.NET framework has complete freedom to create MvcApplication instances as and when they are required and to destroy them when they are no longer needed. This means your global application class must be written so that multiple instances can exist concurrently and that these instances can be used to process several requests sequentially before they are destroyed. The only thing you can rely on is that each instance will be used to process one request at a time, meaning you have to worry only about concurrent access to data objects that are shared (I show you an example of this issue when I introduce application-wide state data in Chapter 10).

The ASP.NET framework triggers these events to chart the path of a request through the processing life cycle. You can handle these events in the global application class, in a module, or in a handler. I introduce modules and handlers in the following section and describe them in depth in Chapters 4 and 5.

Understanding Modules and Handlers

In the following sections, I show you how to respond to the request life-cycle events directly in the global application class. This is a good start for exploring the life-cycle events, but it is suitable for only the simplest of interactions with requests. Any serious request handling functionality tends to consume a number of life-cycle events, which means that the global application class quickly becomes a morass of code that handles the same events to handle requests in different ways. The ASP.NET framework deals with this by supporting modules, which are self-contained classes that receive the life-cycle events and can monitor and manipulate requests. Many of the most important ASP.NET platform services rely on the module functionality to prepare a request early in its life cycle. Examples are the state data and security services, which include modules that respond to events such as AcquireRequestState and AuthenticateRequest to add data to a request before it is handled by the MVC framework. Modules can interact with requests—and the associated responses—at any point in the life cycle, and I describe them in detail in Chapter 4.

The ASP.NET framework also supports a component called a handler. Handlers are responsible for generating a response for a request. The handler for the MVC framework is the component responsible for locating a controller and action method to service a request and rendering the view that the action method specifies. The ASP.NET framework supports multiple handlers, which is why it is possible to mix and match development frameworks such as MVC, Web API, and Web Forms within the same application. The handler is linked to four of the request life-cycle events. The MapRequestHandler and PostMapRequestHandler events are triggered before and after a handler is selected for the request, and the PreRequestHandlerExecute and PostRequestHandlerExecute events are triggered before and after the handler is asked to generate a response for the request. I describe how handlers work in detail in Chapter 5.

The reason that I have introduced modules and handlers in this chapter is because doing so allows me to illustrate the request life cycle more completely, as shown in Figure 3-5.

9781430265412_Fig03-05.jpg

Figure 3-5. Adding the request handling process to the life-cycle diagram

Don’t worry if this seems complex; it will start to make sense as I explain how these events are handled and as you see how various ASP.NET platform services are implemented.

Notice that the global application class is instantiated by both the application and request life cycles. Not only does the ASP.NET framework create multiple instances to service parallel requests, but it also creates separate instances to support each life cycle. You will see the practical impact of this in the “Handling Property Exceptions” section later in this chapter.

Handling Request Life-Cycle Events Using Special Methods

To handle these events in the global application class, you create a method with a name that starts with Application_, followed by the event name, such as Application_BeginRequest, for example. As with the Application_Start and Application_End methods, the ASP.NET framework finds the methods and invokes them when the event they correspond to is triggered. In Listing 3-4, you can see how I have updated the global application class so it handles some of the events in the table (and removed the statements that cause the debugger to break).

Listing 3-4.  Handling Request life-Cycle Events in the Global.asax.cs File

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
 
namespace SimpleApp {
    public class MvcApplication : System.Web.HttpApplication {
 
        protected void Application_Start() {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
 
        protected void Application_BeginRequest() {
            RecordEvent("BeginRequest");
        }
 
        protected void Application_AuthenticateRequest() {
            RecordEvent("AuthenticateRequest");
        }
 
        protected void Application_PostAuthenticateRequest() {
            RecordEvent("PostAuthenticateRequest");
        }
 
        private void RecordEvent(string name) {
            List<string> eventList = Application["events"] as List<string>;
            if (eventList == null) {
                Application["events"] = eventList = new List<string>();
            }
            eventList.Add(name);
        }
    }
}

I have defined a method called RecordEvent that accepts the name of an event and stores it using one of the ASP.NET state management features. I describe these features in detail in Chapter 10, but the one I have used in this example—accessed via the Application property of the HttpApplication class—stores data in a way that makes it available throughout the application.

image Caution  Don’t use the Application property without reading Chapter 10. I am using this feature without taking precautions that are essential in real projects.

I call the RecordEvent method from three other methods I added to the global application class. These events will be called when the BeginRequest, AuthenticateRequest, and PostAuthenticateRequest events are triggered. I don’t have to explicitly register these methods as event handlers; the ASP.NET framework locates and invokes these methods automatically.

Displaying the Event Information

To display information about the events that my code receives, I need to make changes to the Home controller and its Index view. In Listing 3-5, you can see how I retrieve the event state data and pass it to the view as the model object in the Home controller.

Listing 3-5.  Getting the Event Information in the Controllers/HomeController.cs File

using System.Web.Mvc;
using SimpleApp.Models;
 
namespace SimpleApp.Controllers {
    public class HomeController : Controller {
 
        public ActionResult Index() {
            return View(HttpContext.Application["events"]);
        }
 
        [HttpPost]
        public ActionResult Index(Color color) {
            Color? oldColor = Session["color"] as Color?;
            if (oldColor != null) {
                Votes.ChangeVote(color, (Color)oldColor);
            } else {
                Votes.RecordVote(color);
            }
            ViewBag.SelectedColor = Session["color"] = color;
            return View(HttpContext.Application["events"]);
        }
    }
}

To access the data I stored in the global application class, I have to use the HttpContext.Application property, which I describe later in this chapter as part of the context objects that the ASP.NET framework provides and whose functionality I describe in Chapter 10. In Listing 3-6, you can see how I have updated the Razor view associated with the controller so that details of the events are displayed in the browser.

Listing 3-6.  Displaying the Event Information in the Views/Home/Index.cshtml File

@using SimpleApp.Models
@model List<string>
@{ Layout = null; }
 
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Vote</title>
    <link href="∼/Content/bootstrap.min.css" rel="stylesheet" />
    <link href="∼/Content/bootstrap-theme.min.css" rel="stylesheet" />
</head>
<body class="container">
    <div class="panel panel-primary">
        @if (ViewBag.SelectedColor == null) {
            <h4 class="panel-heading">Vote for your favorite color</h4>
        } else {
            <h4 class="panel-heading">Change your vote from @ViewBag.SelectedColor</h4>
        }
 
        <div class="panel-body">
 
            @using (Html.BeginForm()) {
                @Html.DropDownList("color",
                        new SelectList(Enum.GetValues(typeof(Color))), "Choose a Color",
                            new { @class = "form-control" })
                <div>
                    <button class="btn btn-primary center-block"
                            type="submit">
                        Vote
                    </button>
                </div>
            }
 
        </div>
    </div>
 
    <div class="panel panel-primary">
        <h5 class="panel-heading">Results</h5>
        <table class="table table-condensed table-striped">
            @foreach (Color c in Enum.GetValues(typeof(Color))) {
                <tr><td>@c</td><td>@Votes.GetVotes(c)</td></tr>
            }
        </table>
    </div>
 
    <div class="panel panel-primary">
        <h5 class="panel-heading">Events</h5>
        <table class="table table-condensed table-striped">
            @foreach (string eventName in Model) {
                <tr><td>@eventName</td></tr>
            }
        </table>
    </div>
</body>
</html>

The list of event names is passed to the view as the model object, and I use a Razor foreach loop to generate rows in an HTML table element. The result is that the view generates HTML that describes the events that have been recorded by the global application class, as shown in Figure 3-6.

9781430265412_Fig03-06.jpg

Figure 3-6. Displaying details of life-cycle events

image Tip  This technique can be used only for events up to PreRequestHandlerExecute in the sequence shown in Table 3-5. This is because the action method in the controller is called between the PreRequestHandlerExecute and PostRequestHandlerExecute events and so subsequent events are triggered after the response has been produced.

Handling Request Life-Cycle Events Without Special Methods

The HttpApplication class, which is the base for the global application class, defines regular C# events that can be used instead of special methods, as shown in Listing 3-7. The choice between the standard C# events and the special methods is a personal one, and you can mix and match techniques if you want.

Listing 3-7.  Using C# Events in the Global.asax.cs File

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
 
namespace SimpleApp {
    public class MvcApplication : System.Web.HttpApplication {
 
        public MvcApplication() {
            BeginRequest += (src, args) => RecordEvent("BeginRequest");
            AuthenticateRequest += (src, args) => RecordEvent("AuthentucateRequest");
            PostAuthenticateRequest += (src, args) =>
                RecordEvent("PostAuthenticateRequest");
        }
 
        protected void Application_Start() {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
 
        private void RecordEvent(string name) {
            List<string> eventList = Application["events"] as List<string>;
            if (eventList == null) {
                Application["events"] = eventList = new List<string>();
            }
            eventList.Add(name);
        }
    }
}

I have added a constructor to the MvcApplication class and have used it to set up event handlers for three of the request life-cycle events. For all three events I have used lambda expressions that call the RecordEvent method, storing the name of the event so that it can be read back by the controller, just as in the previous example.

image Tip  There are no standard C# events to replace the Application_Start and Application_End methods. You can receive these notifications only via special methods.

Using a Single Method to Handle Multiple Events

You can use two properties defined by the SystemWeb.HttpContext class if you want to use a single method to handle multiple life-cycle events without relying on lambda expressions. The HttpContext class provides details of the current request and the state of the application, and I describe it later in this chapter. For the moment, however, the two properties that relate to processing life-cycle events are shown in Table 3-6.

Table 3-6. The HttpContext Properties for Determining the Current Application Event

Name

Description

CurrentNotification

This property indicates the current application event using a value from the System.Web.RequestNotification enumeration.

IsPostNotification

This property returns true if the current application event is the Post<Name> variant of the event returned by the CurrentNotification property.

These two properties are a little odd because both must be used to figure out which event is being handled. The CurrentNotification property returns a value from the RequestNotification enumeration, which defines a subset of the HttpApplication events. The value that this property returns is used with the IsPostNotification property to figure out whether the event that has been triggered is an event like AcquireRequestState or its paired event, PostAcquireRequestState. The HttpApplication class provides access to an HttpContext object through the Context property, and Listing 3-8 shows how to handle events in this way.

Listing 3-8.  Handling Life-Cycle Events in a Single Method in the Global.asax.cs File

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
 
namespace SimpleApp {
    public class MvcApplication : System.Web.HttpApplication {
 
        public MvcApplication() {
            BeginRequest += RecordEvent;
            AuthenticateRequest += RecordEvent;
            PostAuthenticateRequest += RecordEvent;
        }
 
        protected void Application_Start() {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
 
        private void RecordEvent(object src, EventArgs args) {
            List<string> eventList = Application["events"] as List<string>;
            if (eventList == null) {
                Application["events"] = eventList = new List<string>();
            }
            string name = Context.CurrentNotification.ToString();
            if (Context.IsPostNotification) {
                name = "Post" + name;
            }
            eventList.Add(name);
        }
    }
}

I changed the signature of the RecordEvent method so that it accepts the standard event handler arguments: an object that represents the source of the event and an EventArgs object that describes the event. I don’t use these, however, since the information about the event is exposed through the Context.CurrentNotification and Context.IsPostNotification properties.

I don’t know why Microsoft has implemented the events this way, but this is the approach you must use if you don’t like special methods or lambda expressions. Notice that in this listing I had to call the ToString method on the Context.CurrentNotification method; this is required because the CurrentNotification property returns a value from the System.Web.RequestNotification enumeration, which I have described in Table 3-7.

Table 3-7. The Values of the RequestNotification Enumeration

Value

Description

BeginRequest

Corresponds to the BeginRequest event

AuthenticateRequest

Corresponds to the AuthenticateRequest and PostAuthenticateRequest events

AuthorizeRequest

Corresponds to the AuthorizeRequest event

ResolveRequestCache

Corresponds to the ResolveRequestCache and PostResolveRequestCache events

MapRequestHandler

Corresponds to the MapRequestHandler and PostMapRequestHandler events

AcquireRequestState

Corresponds to the AcquireRequestState and PostAcquireRequestState event

PreExecuteRequestHandler

Corresponds to the PreExecuteRequestHandler event

ExecuteRequestHandler

Corresponds to the ExecuteRequestHandler event

ReleaseRequestState

Corresponds to the ReleaseRequestState and PostReleaseRequestState event

UpdateRequestCache

Corresponds to the UpdateRequestCache event

LogRequest

Corresponds to the LogRequest event

EndRequest

Corresponds to the EndRequest event

SendResponse

Indicates that the response is being sent—corresponds loosely to the PreSendRequestHeaders and PreSendRequestContent events

The ASP.NET Context Objects

ASP.NET provides a set of objects that are used to provide context information about the current request, the response that will be returned to the client, and the web application itself; indirectly, these context objects can be used to access core ASP.NET framework features. Table 3-8 summarizes the context objects.

Table 3-8. Putting the Request Life Cycle in Context

Question

Answer

What are they?

The context objects provide information about the application, the current request, and the response that is being prepared for it. They also provide access to the most important ASP.NET platform services such as security and state data.

Why should I care?

You use the context objects within MVC framework controllers and views to vary your application responses based on the request or the state of the application. You also use these objects when creating modules and handlers, which I describe in Chapters 4 and 5.

How are they used by the MVC framework?

The MVC framework uses the context objects to process requests and build on ASP.NET services such as mobile device detection (which I describe in Chapter 7).

Understanding the ASP.NET Context Objects

The class at the heart of the context is System.Web.HttpContext. It is universally available throughout the ASP.NET framework and the MVC framework, and it acts as a gateway to other context objects and to ASP.NET platform features and services. In fact, the HttpContext class is so central to the ASP.NET framework that the most important properties are like a map to the rest of this book, as Table 3-9 illustrates.

Table 3-9. The Most Commonly Used HttpContext Members

Name

Description

Application

Returns the HttpApplicationState object used to manage application state data (see Chapter 10).

ApplicationInstance

Returns the HttpApplication object associated with the current request (described later in this chapter).

Cache

Returns a Cache object used to cache data. See Chapter 11 for details.

Current

(Static.) Returns the HttpContext object for the current request.

CurrentHandler

Returns the IHttpHandler instance that will generate content for the request. See Chapter 5 for details of handlers and Chapter 6 for information about how to preempt the handler selection process used by the ASP.NET platform.

IsDebuggingEnabled

Returns true if the debugger is attached to the ASP.NET application. You can use this to perform debug-specific activities, but if you do, take care to test thoroughly without the debugger before deployment.

Items

Returns a collection that can be used to pass state data between ASP.NET framework components that participate in processing a request.

GetSection(name)

Gets the specified configuration section from the Web.config file. I show you how to work with the Web.config files in Chapter 9.

Request

Returns an HttpRequest object that provides details of the request being processed. I describe the HttpRequest class later in this chapter.

Response

Returns an HttpResponse object that provides details of the response that is being constructed and that will be sent to the browser. I describe the HttpResponse object later in this chapter.

Session

Returns an HttpSession state object that provides access to the session state. This property will return null until the PostAcquireRequestState application event has been triggered. See Chapter 10 for details.

Server

Returns an HttpServerUtility object that can contain utility functions, the most useful being the ability to control request handler execution (see Chapter 6).

Timestamp

Returns a DateTime object that contains the time at which the HttpContext object was created.

Trace

Used to record diagnostic information. See Chapter 8.

The HttpContext class also defines methods and properties that can be used to manage the request life cycle—like the CurrentNotification and IsPostNotification properties I used when handling life-cycle events with a single method in the previous section. I’ll show you the different context object features, including those defined by HttpContext, in the chapters that are related to their functionality.

As you saw in Listing 3-8, you can get an instance of the HttpContext class using the Context property from within the global application class. This property name is not universal; you will need to use the HttpContext property from within a controller class or a view, for example. If all else fails, you can get the HttpContext object associated with the current request by using the static HttpContext.Current property. For quick reference, Table 3-10 summarizes the ways in which you can get an HttpContext object in the different parts of an application.

Table 3-10. Obtaining an HttpContext in Different ASP.NET/MVC Components

Component

Technique

Controller

Use the HttpContext property defined by Controller, which is the base class for MVC framework controllers.

View

Use the Context property defined by WebViewPage, which is the base class used to compile Razor views.

Global Application Class

Use the Context convenience property defined by the HttpApplication class (which is the base for the global application class).

Module

The Init method is passed an HttpContext object when it is invoked, and the life-cycle event handlers are passed an HttpApplication object, which defines a Context property. See Chapter 4 for details of modules.

Handler

The ProcessRequest method is passed an HttpContext object when it is invoked. See Chapter 5 for details of handlers.

Universally

You can always get the HttpContext object associated with the current request through the static HttpContext.Current property.

image Tip  A new set of context objects is created for every request, and when you obtain HttpRequest or HttpResponse objects, you will receive the instances that relate to the request being processed by the current instance of the global application class. Or, in other words, you don’t have to worry about locating the context objects that relate to a specific request.

Notice that I have not included the model in the list of application components in Table 3-10. You can obtain the HttpContext object for the current request in the model through the static HttpContext.Current property, but I suggest that you don’t because it blurs the separation of concerns between the model and the controller. If the model needs information about a request, then obtain this information from the context objects in the controller and pass it as method arguments to the model. This will ensure that the model doesn’t meddle in the business of controllers and will allow the model to be unit tested without any reference to ASP.NET or the MVC framework.

CONTEXT, BASE, AND WRAPPER CLASSES

The properties that I listed in Table 3-10 do not all return the same type. The properties used in the pre-MVC framework components (the global application class, handlers, and modules) return an HttpContext object, just as you would expect).

These context objects predate the MVC framework and make it hard to perform unit testing because they are tightly coupled, requiring you to create entire sets of context objects each time you want to perform a test.

The properties defined by the MVC framework components—the controller and the view—are used to get context objects and return instances of different classes that are derived from the context classes but provide support for easy unit testing. The HttpContext property in the Controller class returns an instance of the HttpContextBase class. All the context objects are represented by a class with Base appended to the name (HttpRequestBase, HttpResponseBase, and so on) and can more readily instantiated, configured, and mocked for testing purposes.

You will sometimes need to create a Base object from an ASP.NET object; for example, you might have an HttpRequest object but need to call a method that takes an HttpRequestBase object. The ASP.NET class library includes classes with Wrapper appended to the name: HttpContextWrapper, HttpRequestWrapper, and so on. These classes are derived from the Base classes and are used by the MVC framework to present the ASP.NET context classes in an MVC-friendly Base class (so HttpContextWrapper is derived from HttpContextBase, which accepts an instance of HttpContext as constructor arguments). The Wrapper class constructors take context objects as arguments and the properties, and methods of the HttpContextWrapper are passed to the HttpContext instance that it contains.

You can’t unwrap a Base object—converting from HttpRequestBase to HttpRequest, for example. But you can always get the context object you require through the static HttpContext.Current property, which returns an HttpContext object that presents the properties shown in Table 3-9. You will have to use the fully qualified name for this property (System.Web.HttpContext.Current) within controller classes because they define an HttpContext property that returns an HttpContextBase object. In this book, I treat the ASP.NET framework context classes and the MVC Base classes as being identical.

Working with HttpApplication Objects

Many of the classes that you will use in the ASP.NET framework provide convenience properties that are mapped to those defined by the HttpContext class. A good example of this overlap can be seen in HttpApplication, which is the base for the global application class. In Table 3-11, you can see the properties and methods defined by the HttpApplication class, many of which are similar to those defined by HttpContext.

Table 3-11. The Members Defined by the HttpApplication Class

Name

Description

Application

Maps to the HttpContext.Application property, which provides access to application-wide state data, as described in Chapter 10.

CompleteRequest()

Abandons the life cycle for the current request and moves directly to the LogRequest event. See Chapter 6 for details.

Context

Returns the HttpContext object for the current request.

Init()

Called when the Init method has been called on each of the registered modules; see Chapter 4 for details of modules.

Modules

Returns an HttpModuleCollection object that details the modules in the application; see Chapter 4 for details of modules.

RegisterModule(type)

Static method that registers a new module; see Chapter 4 for an example.

Request

Returns the HttpContext.Request value, but throws an HttpException if the value is null.

Response

Returns the HttpContext.Response value, but throws an HttpException if the value is null.

Server

Maps to the HttpContext.Server property. See Chapter 6.

Session

Returns the HttpContext.Session value, but throws an HttpException if the value is null. See Chapter 10.

Most of these members are convenience properties that map to the properties of the HttpContext class, but there are some points to note, as discussed in the following section.

Handling Property Exceptions

The Request, Response, Session, and User properties all return the value of the corresponding properties from the HttpContext class, but with a wrinkle—all of these properties will throw an HttpException if the value they get from HttpContext is null.

This happens because the HttpApplication class receives notifications for two different life cycles: the application life cycle and the request life cycle. Objects that describe a single request are not available when an instance of the global application class is being used to handle application-level events, so the HttpException is thrown if you try to access request-related properties when handling application-level notifications.

The policy of throwing an exception is harsh because it makes it hard to deal with HttpApplication objects of unknown provenance. You can see an example of this issue in Listing 3-9, which shows changes to the global application class.

Listing 3-9.  Writing Code That Deals with Both Kinds of HttpApplication Objects in the Global.asax.cs File

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
 
namespace SimpleApp {
    public class MvcApplication : System.Web.HttpApplication {
 
        public MvcApplication() {
            PostAcquireRequestState += (src, args) => CreateTimeStamp();
        }
 
        protected void Application_Start() {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            CreateTimeStamp();
        }
 
        private void CreateTimeStamp() {
            string stamp = Context.Timestamp.ToLongTimeString();
            if (Session != null) {
                Session["request_timestamp"] = stamp;
            } else {
                Application["app_timestamp"] = stamp;
            }
        }
    }
}

I have removed the code that registers and handles some of the request life-cycle events and have defined a new method that creates a timestamp and stores it as state data. I describe the ASP.NET framework support for state data (including the objects returned by Session and Application properties used here) in Chapter 10, but for now it is enough to know that the Session property will return an object for storing state for individual requests.

image Tip  Notice that I create the request timestamp in response to the PostAcquireRequestState event. This is because the modules that provide state data services for requests are not asked to do so until the AcquireRequestState event; therefore, the Session property will return null, even if the global application class instance has been created to process events for a request. Don’t worry if this doesn’t make sense now; I explain how modules work in Chapter 4 and describe the different types of state data in Chapter 10.

The new method, called CreateTimeStamp, aims to reduce code duplication by dealing with application-level and request-level timestamps with the same set of statements. This code will throw an exception as soon as the application is started because it attempts to read the Session property for an instance of the global application class that has been created to deal with the Application_Start method being called.

I could address this by using try...catch blocks, but that would be bad practice because exception handling should not be used to manage the regular and expected flow of an application. Instead, I can use the equivalent properties directly on HttpContext, as shown in Listing 3-10. This allows me to use one method to create both kinds of timestamps in a single method without having to worry about exceptions.

Listing 3-10.  Using the Properties Defined by the HttpContext Class in the Global.asax.cs File

...
private void CreateTimeStamp() {
    string stamp = Context.Timestamp.ToLongTimeString();
    if (Context.Session != null) {
        Session["request_timestamp"] = stamp;
    } else {
        Application["app_timestamp"] = stamp;
    }
}
...

image Caution  The change in Listing 3-10 won’t work until you have also applied the changes in Listing 3-11.

Notice that I only have to change the initial check for the Session property; if it isn’t null, I can use the Session property defined by the HttpApplication class. The Application property will always return an HttpApplicationState object because, as I explain in Chapter 10, the state management feature it provides is application-wide and is initialized when the application is started.

You need to pay attention to this issue only when working directly with the global application class where you can be working with objects that are not associated with an individual request. Elsewhere in ASP.NET, and especially within MVC framework controller classes, you will always be working with context objects that represent a request. As an example, Listing 3-11 shows the changes I made to the HomeController class to display the timestamps created in the previous listing.

Listing 3-11.  Displaying Timestamps in the HomeController.cs File

using System.Collections.Generic;
using System.Web.Mvc;
using SimpleApp.Models;
 
namespace SimpleApp.Controllers {
    public class HomeController : Controller {
 
        public ActionResult Index() {
            return View(GetTimeStamps());
        }
 
        [HttpPost]
        public ActionResult Index(Color color) {
            Color? oldColor = Session["color"] as Color?;
            if (oldColor != null) {
                Votes.ChangeVote(color, (Color)oldColor);
            } else {
                Votes.RecordVote(color);
            }
            ViewBag.SelectedColor = Session["color"] = color;
            return View(GetTimeStamps());
        }
 
        private List<string> GetTimeStamps() {
            return new List<string> {
                string.Format("App timestamp: {0}",
                    HttpContext.Application["app_timestamp"]),
                string.Format("Request timestamp: {0}", Session["request_timestamp"]),
            };
        }
    }
}

I have used the C# string composition feature, available through the static String.Format method, to create a list of messages that will be passed to the view. The Controller class that is the base for MVC framework controllers provides a Session convenience property that corresponds to HttpContext.Session (but I have to access HttpContext.Application directly since there is no corresponding convenience property).

You can see the timestamps by starting the application, as illustrated by Figure 3-7. Both values will be the same initially, but if you reload the browser window a few times, you will see that the application timestamp remains constant while the request timestamp is updated each time.

9781430265412_Fig03-07.jpg

Figure 3-7. Displaying application and request timestamps

image Note  If you find that both timestamps update for every request, then you may have forgotten to disable the Visual Studio Browser Link feature as described in Chapter 1. The Browser Link feature relies on injecting JavaScript code into the HTML sent to the browser that establishes a connection back to the server and waits for a change notification. The global application class handles all requests and not just those intended for the MVC framework, so the GetTimeStamps method is called twice for each request. Browser Link uses a feature called sessionless controllers that prevents the Context.Session property from ever being set, which has the effect of confusing the code in Listing 3-10 and updating the application-level timestamp. I describe sessionless controllers in Chapter 10.

Working with HttpRequest Objects

The HttpRequest object describes a single HTTP request as it is being processed. Table 3-12 describes the HttpRequest properties that provide information about the current request. (The HttpRequest class defines methods and some other properties, but I describe these in later chapters in the context of the platform features they correspond to.)

Table 3-12. The Descriptive Properties Defined by the HttpRequest Class

Name

Description

AcceptTypes

Returns a string array containing the MIME types accepted by the browser.

Browser

Returns an HttpBrowserCapabilities object that describes the capabilities of the browser; see Chapter 7 for more details.

ContentEncoding

Returns a System.Text.Encoding object that represents the character set used to encode the request data.

ContentLength

Returns the number of bytes of content in the request.

ContentType

Returns the MIME type of the content included in the request.

CurrentExecutionFilePathExtension

Returns the file extension component of the requested URL.

Headers

Returns a collection containing the request headers.

HttpMethod

Returns the HTTP method used to make the request (GET, POST, and so on).

InputStream

Returns a stream that can be used to read the contents of the request.

IsLocal

Returns true when the request has originated from the local machine.

MapPath(path)

Translates a file name within the project to an absolute path. See Chapter 11 for an example.

RawUrl

Returns the part of the URL that follows the hostname. In other words, for http://apress.com:80/books/Default.aspx, this property would return /books/Default.aspx.

RequestContext

Returns a RequestContext object that provides access to the routing information for a request. I demonstrate the use of the RequestContext object in Chapter 6.

Url

Returns the request URL as a System.Uri object.

UrlReferrer

Returns the referrer URL as a System.Uri object.

UserAgent

Returns the user-agent string supplied by the browser.

UserHostAddress

Returns the IP address of the remote client, expressed as a string.

UserHostName

Returns the DNS name of the remote client.

UserLanguages

Returns a string array of the languages preferred by the browser/user.

To demonstrate the use of the HttpRequest class, I have modified the Index.cshtml view so that it displays some of the request properties, as shown in Listing 3-12.

Listing 3-12.  Displaying Request Details in the Index.cshtml File

@using SimpleApp.Models
@model List<string>
@{ Layout = null; }
 
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Vote</title>
    <link href="∼/Content/bootstrap.min.css" rel="stylesheet" />
    <link href="∼/Content/bootstrap-theme.min.css" rel="stylesheet" />
</head>
<body class="container">
    <div class="panel panel-primary">
        @if (ViewBag.SelectedColor == null) {
            <h4 class="panel-heading">Vote for your favorite color</h4>
        } else {
            <h4 class="panel-heading">Change your vote from @ViewBag.SelectedColor</h4>
        }
 
        <div class="panel-body">
 
            @using (Html.BeginForm()) {
                @Html.DropDownList("color",
                        new SelectList(Enum.GetValues(typeof(Color))), "Choose a Color",
                            new { @class = "form-control" })
                <div>
                    <button class="btn btn-primary center-block"
                            type="submit">
                        Vote
                    </button>
                </div>
            }
 
        </div>
    </div>
 
    <div class="panel panel-primary">
        <h5 class="panel-heading">Results</h5>
        <table class="table table-condensed table-striped">
            @foreach (Color c in Enum.GetValues(typeof(Color))) {
                <tr><td>@c</td><td>@Votes.GetVotes(c)</td></tr>
            }
        </table>
    </div>
 
    <div class="panel panel-primary">
        <h5 class="panel-heading">Request Properties</h5>
        <table class="table table-condensed table-striped">
            <tr><th>Property</th><th>Value</th></tr>
            <tr><td>HttpMethod</td><td>@Request.HttpMethod</td></tr>
            <tr><td>IsLocal</td><td>@Request.IsLocal</td></tr>
            <tr><td>RawURL</td><td>@Request.RawUrl</td></tr>
        </table>
    </div>
</body>
</html>

Notice that I am able to access the HttpRequest object using a property called Request, like this:

...
<tr><td>HttpMethod</td><td>@Request.HttpMethod</td></tr>
...

The HttpRequest object is so frequently used that some application components, including Razor views, provide convenience properties so that you don’t have to obtain an HttpContext object just to get an instance of HttpRequest. Table 3-13 summarizes the convenience properties that are available for HttpRequest objects.

Table 3-13. Obtaining an HttpRequest Object in Different ASP.NET/MVC Components

Component

Technique

Controller

Use the Request convenience property.

View

Use the Request convenience property.

Global Application Class

Use the Request convenience property.

Module

No convenience property is available. Use the HttpContext.Request property. (Modules are described in Chapter 4.)

Handler

No convenience property is available. Use the HttpContext.Request property. (Handlers are described in Chapter 5.)

Universally

You can always get the HttpRequest object for the current request through the static HttpContext.Current.Request property.

Figure 3-8 shows the request property values displayed by the view, for both an initial GET request and the POST request that results when the user selects a color and clicks the Vote button.

9781430265412_Fig03-08.jpg

Figure 3-8. Displaying details of the request

In addition to the properties shown in Table 3-12, there are some properties that provide access to the data included in a request. I have shown these in Table 3-14, but they are not usually used directly in MVC controllers because model binding, which I describe in Pro ASP.NET MVC 5, is easier to work with. These properties are sometimes used in modules, however.

Table 3-14. Additional Properties Defined by the HttpRequest Class

Name

Description

Files

Returns a collection of files sent by the browser in a form.

Form

Provides access to the raw form data.

Params

A collection of the combined data items from the query string, form fields, and cookies. You can also use an array-style indexer directly on the HttpRequest object, such that Request["myname"] is the same as Request.Params["myname"].

QueryString

Returns a collection of the query string parameters; this property isn’t usually used directly in MVC applications.

Working with HttpResponse Objects

The HttpResponse object is the counterpart to HttpRequest and represents the response as it is being constructed. It also provides methods and properties that let you customize the response, something that is rarely required when using MVC views but that can be useful when working with other components, such as modules and handlers (described in Chapters 4 and 5).

Like HttpRequest, this class is essential to the way that ASP.NET processes requests and is used extensively within the MVC framework to generate the HTML (or other data) that is returned to clients. I have described the most important methods and properties in Table 3-15.

Table 3-15. The Most Useful Members of the HttpResponse Class

Name

Description

AppendHeader(name, val)

Convenience method to add a new header to the response.

BufferOutput

Gets or sets a value indicating whether the request should be buffered completely before it is sent to the browser. The default value is true. Changing this to false will prevent subsequent modules and handlers from being able to alter the response.

Cache

Returns an HttpCachePolicy object that specifies the caching policy for the response. I describe the ASP.NET caching services in Chapters 11 and 12.

CacheControl

Gets or set the cache-control HTTP header for the response.

Charset

Gets or sets the character set specified for the response.

Clear()

The Clear and ClearContent methods are equivalent, and they remove any content from the response.

ClearContent()

ClearHeaders()

Removes all of the headers from the response.

ContentEncoding

Gets or sets the encoding used for content in the response.

Headers

Returns the collection of headers for the response.

IsClientConnected

Returns true if the client is still connected to the server.

IsRequestBeingDirected

Returns true if the browser will be sent a redirection.

Output

Returns a TextWriter that can be used to write text to the response.

OutputStream

Returns a Stream that can be used to write binary data to the response.

RedirectLocation

Gets or sets the value of the HTTP Location header.

Status

Gets or sets the status for the response; the default is 200 (OK).

StatusCode

Gets or sets the numeric part of the status; the default is 200.

StatusDescription

Gets or sets the text part of the status; the default is (OK).

SuppressContent

When set to true, this property prevents the response content from being sent to the client.

Write(data)

Writes data to the response output stream.

WriteFile(path)

Writes the contents of the specified file to the output stream.

In Table 3-16, I have summarized the convenience properties for a range of ASP.NET and MVC framework components.

Table 3-16. Obtaining an HttpResponse Object in Different ASP.NET/MVC Components

Component

Technique

Controller

Use the Response convenience property.

View

Use the Response convenience property.

Global Application Class

Use the Response convenience property.

Module

No convenience property is available. Use the HttpContext.Response property. (Modules are described in Chapter 4.)

Handler

No convenience property is available. Use the HttpContext.Response property. (Handlers are described in Chapter 5.)

Universally

You can always get the current HttpResponse object through the static HttpContext.Current.Response property.

Summary

In this chapter, I described the application and request life cycles, which provide the foundation for much of the functionality provided by the ASP.NET platform—and, by implication, the MVC framework. I introduced the global application class and explained the role of the special Application_Start and Application_End methods and their relationship to the application life cycle. I also described the request life-cycle events and explained how they are used to move a request through ASP.NET, something that will become clearer once I have described modules and handlers in later chapters. I finished the chapter by providing an overview of the context objects that ASP.NET provides for describing the state of the application, the current request being processed, and the response that is being prepared for it. You’ll see these context objects throughout the book because they underpin so much of what ASP.NET can do. In the next chapter, I describe modules, which are self-contained components that handle the request life-cycle events.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.52.203