Placing a Web Method in a Page

In most cases, it makes sense to create a separate web service to handle your ASP.NET AJAX callbacks.
This approach generally results in clearer pages and makes it easier to debug and refine your code.
However, in some situations you may decide you have one or more web methods that are designed
explicitly for use on a single page and that really shouldn’t be reused in other parts of the application. In
this case, you may choose to create a dedicated web service for each page, or you might choose to move
the web service code into the page.
Placing the web method code in the page is easy‚ÄĒin fact, all you need is a simple bit of cut-andpaste.
First, copy your web method (complete with the WebMethod attribute) into the code-behind class
for your page. Then, change it to a static method, and add the System.Web.Script.Services.ScriptMethod
attribute. Here’s an example where the web method (named GetTerritoriesInRegion) is placed in a web
page named WebServiceCallback_PageMethods:
public partial class WebServiceCallback_PageMethods : System.Web.UI.Page
{
   [System.Web.Services.WebMethod()]
   [System.Web.Script.Services.ScriptMethod()]
   public static List<Territory> GetTerritoriesInRegion(int regionID)
   {
      // Farm the work out to the web service class.
      TerritoriesService service = new TerritoriesService();
      return service.GetTerritoriesInRegion(regionID);
   }
}
Next, set the ScriptManager.EnablePageMethods property to true, and remove the reference in the
<Services> section of the ScriptManager (assuming you don’t want to use any non-page web services):

<asp:ScriptManager ID=”ScriptManager1″ runat=”server” EnablePageMethods=”true”>
</asp:ScriptManager>
Finally, change your JavaScript code so it calls the method through the PageMethods object, as
shown here:

PageMethods.GetTerritoriesInRegion(regionID, OnRequestComplete, OnError);

The PageMethods object exposes all the web methods you’ve added to the current web page.
One advantage of placing a web method in a page is that the method is no longer exposed through
an .asmx file. As a result, it’s not considered part of a public web service, and it’s not as easy for someone
else to discover. This is appealing if you’re trying to hide your web services from curious users.
Another reason you might choose to code your web methods in the page class is to read values from
view state or the controls on the page. When you trigger a page method, a stripped-down version of the
page life cycle executes, just like with the ASP.NET client callback feature you saw in Chapter 29. Of
course, there’s no point in trying to modify page details because the page isn’t being rerendered, so any
changes you make will simply be discarded.

Loading a custom control at runtime

You can load a server control to your page using the constructor of the control. For example a Literal control can be loaded as follows:

plhContainer.Controls.Add(
new Literal
{
Text = string.Format("{0}.{1}",
method.ReflectedType.FullName, method.Name)
});

When it comes to a custom control you also want the markup code of you custom control to be loaded too. So you need to tell the Page o load it for you:


var nameBox = (NameBox) Page.LoadControl("NameBox.ascx");

Basically, you could do this at any time, but it is recommanded to do this at Page_Load the reason is that this is the best place for the control to restore its state and receive postback events. Also the binding will take place after this method. Look at my previous post for the sequence of the events and method calls when page gets loaded.
It is also recomannded to set a unique ID to that control if you need to find that later using FindControl, or some one else want to find where you have put it ūüôā

So, my load method will look like this:


private void LoadMyControls()
{
var nameBox = (NameBox) Page.LoadControl("NameBox.ascx");
// Give the user control a unique name by setting its ID property.
// You can use this information to retrieve a reference to the control
// when you need it with the Page.FindControl() method.
nameBox.ID = "nameBox";
nameBox.FirstName = "Asghar";
nameBox.LastName = "Panahy";
nameBox.ChangeRequest += ChangeName;
plhContainer.Controls.Add(nameBox);
}

When I call LoadMyControls() in Page_Load method, I see the following sequence in my output:


STARTING: MCTS._70_515.Resources._Default.Page_Init
STARTING: MCTS._70_515.Resources._Default.OnInit
STARTING: MCTS._70_515.Resources._Default.Page_Load
STARTING: MCTS._70_515.Resources.NameBox.Page_Init
STARTING: MCTS._70_515.Resources.NameBox.OnInit
STARTING: MCTS._70_515.Resources._Default.OnLoad
STARTING: MCTS._70_515.Resources.NameBox.Page_Load
STARTING: MCTS._70_515.Resources.NameBox.OnDataBinding
STARTING: MCTS._70_515.Resources.NameBox.get_LastName
STARTING: MCTS._70_515.Resources.NameBox.OnLoad
STARTING: MCTS._70_515.Resources._Default.OnPreRender
STARTING: MCTS._70_515.Resources.NameBox.OnPreRender
STARTING: MCTS._70_515.Resources.NameBox.OnUnload
STARTING: MCTS._70_515.Resources._Default.OnUnload

Notice that the custom control gets its data binded just after the page has passed Loading which is a good thing.

ASP Page en Control Lifecycle

When I put a logging in a couple of override methods of a NameBox control and load the page containing the custom control I see the following:

STARTING: MCTS._70_515.Resources.NameBox.Page_Init
STARTING: MCTS._70_515.Resources.NameBox.OnInit
STARTING: MCTS._70_515.Resources.NameBox.Page_Load
STARTING: MCTS._70_515.Resources.NameBox.OnDataBinding
STARTING: MCTS._70_515.Resources.NameBox.get_LastName
STARTING: MCTS._70_515.Resources.NameBox.OnLoad
STARTING: MCTS._70_515.Resources.NameBox.OnPreRender
STARTING: MCTS._70_515.Resources.NameBox.OnUnload

It is important to note that Page_Load is long before OnLoad method, similar to Page_Init versus OnInit.

Next I click on the change button to post back to handle the event and I see the following:

STARTING: MCTS._70_515.Resources.NameBox.Page_Init
STARTING: MCTS._70_515.Resources.NameBox.OnInit
STARTING: MCTS._70_515.Resources.NameBox.Page_Load
STARTING: MCTS._70_515.Resources.NameBox.OnDataBinding
STARTING: MCTS._70_515.Resources.NameBox.get_LastName
STARTING: MCTS._70_515.Resources.NameBox.OnLoad
STARTING: MCTS._70_515.Resources.NameBox.ChangeRequestClicked
STARTING: MCTS._70_515.Resources.NameBox.OnPreRender
STARTING: MCTS._70_515.Resources.NameBox.OnUnload

Next, I add some similar loggging to the page that contains the control and see what happens when I load the page:


STARTING: MCTS._70_515.Resources.NameBox.Page_Init
STARTING: MCTS._70_515.Resources.NameBox.OnInit
STARTING: MCTS._70_515.Resources._Default.Page_Init
STARTING: MCTS._70_515.Resources._Default.OnInit
STARTING: MCTS._70_515.Resources._Default.Page_Load
STARTING: MCTS._70_515.Resources._Default.OnLoad

STARTING: MCTS._70_515.Resources.NameBox.Page_Load
STARTING: MCTS._70_515.Resources.NameBox.OnDataBinding
STARTING: MCTS._70_515.Resources.NameBox.get_LastName
STARTING: MCTS._70_515.Resources.NameBox.OnLoad
STARTING: MCTS._70_515.Resources._Default.OnPreRender
STARTING: MCTS._70_515.Resources.NameBox.OnPreRender
STARTING: MCTS._70_515.Resources.NameBox.OnUnload
STARTING: MCTS._70_515.Resources._Default.OnUnload

So, The OnLoad method gets called after binding whereas the Page_Load is about before the binding. Obviously, if you need to initialize your data, you better put your code in OnInit rather that OnLoad or Page_Load.
Another important thig here is the PreRender methods. These methods get called after load has been completed and the data is assumed to be in place.

Now let’s see what happens when I click a button on the control which causes a postback:


STARTING: MCTS._70_515.Resources.NameBox.Page_Init
STARTING: MCTS._70_515.Resources.NameBox.OnInit
STARTING: MCTS._70_515.Resources._Default.Page_Init
STARTING: MCTS._70_515.Resources._Default.OnInit
STARTING: MCTS._70_515.Resources._Default.Page_Load
STARTING: MCTS._70_515.Resources._Default.OnLoad

STARTING: MCTS._70_515.Resources.NameBox.Page_Load
STARTING: MCTS._70_515.Resources.NameBox.OnDataBinding
STARTING: MCTS._70_515.Resources.NameBox.get_LastName
STARTING: MCTS._70_515.Resources.NameBox.OnLoad
STARTING: MCTS._70_515.Resources.NameBox.ChangeRequestClicked
STARTING: MCTS._70_515.Resources._Default.ChangeName
STARTING: MCTS._70_515.Resources._Default.OnPreRender

STARTING: MCTS._70_515.Resources.NameBox.OnPreRender
STARTING: MCTS._70_515.Resources.NameBox.OnUnload
STARTING: MCTS._70_515.Resources._Default.OnUnload

Both event handlers in the control and in the page are called after all controls and the page has been loaded and the data is binded too, just before the rendering take place.

Implementing custom FileCacheProvider

This is a summerized version of what you can find in the APress book Chapter 11.

If you want to cach some pages or all, you need to first tell the ASP.NET to do so. As the caching starts before rendering the page, you can not specifiy this in the page markup, rather it will be mentioned in the Global.asax.cs
        public override string GetOutputCacheProviderName(HttpContext context)
        {
            // Get the page.
            string pageAndQuery = System.IO.Path.GetFileName(context.Request.Path);
            if (pageAndQuery.StartsWith(“OutputCaching.aspx”))
                return “FileCache”;
            else
                return base.GetOutputCacheProviderName(context);
        }
This tells to ASP.NET to use FileCache mechanics for cachin OutputCaching.aspx page. The rest will just follow the default.
Next step is to specify where to find the implementation of FileCache. This is mentioned in the web.config as follows:
    <system.web>
      <caching>
      <outputCache defaultProvider=”FileCache”>
        <providers>
          <add name=”FileCache” type=”MyCompany.MyProject.Caching.FileCacheProvider” 
               cachePath=”~/Cache” />
        </providers>
      </outputCache>
    </caching>
  </system.web>
Notice that the cachePath is not a recognized attribute but we may introduce it to get the parameters. In this case it refers to an existing folder on the root of the website.
Next step is to implement the provider. This involves to implement a class that inherits from OutputCacheProvider and overrides the methods as follows:
    public class FileCacheProvider : OutputCacheProvider
    {
        // The location where cached files will be placed.
        public string CachePath
        { get; set; }
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection attributes)
        {
            base.Initialize(name, attributes);
            // Retrieve the web.config settings.
            CachePath = HttpContext.Current.Server.MapPath(attributes[“cachePath”]);
        }
        public override object Add(string key, object entry, System.DateTime utcExpiry)
        {
            // Transform the key to a unique filename.
            string path = ConvertKeyToPath(key);
            // Set it only if it is not already cached.
            if (!File.Exists(path))
            {
                Set(key, entry, utcExpiry);
            }
            return entry;
        }
        public override object Get(string key)
        {
            string path = ConvertKeyToPath(key);
            if (!File.Exists(path)) return null;
            CacheItem item = null;
            using (FileStream file = File.OpenRead(path))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                item = (CacheItem)formatter.Deserialize(file);
            }
            // Remove expired items.
            if (item.ExpiryDate <= DateTime.Now.ToUniversalTime())
            {
                Remove(key);
                return null;
            }
            return item.Item;
        }
        public override void Set(string key, object entry, System.DateTime utcExpiry)
        {
            CacheItem item = new CacheItem(entry, utcExpiry);
            string path = ConvertKeyToPath(key);
            // Overwrite it, even if it already exists.
            using (FileStream file = File.OpenWrite(path))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(file, item);
            }
        }
        public override void Remove(string key)
        {
            string path = ConvertKeyToPath(key);
            if (File.Exists(path)) File.Delete(path);
        }
        private string ConvertKeyToPath(string key)
        {
            // Flatten it to a single file name, with no path information.
            string file = key.Replace(‘/’, ‘-‘);
            // Add .txt extension so it’s not confused with a real ASP.NET file.
            file += “.txt”;
            return Path.Combine(CachePath, file);
        }
    }
The CachePath will be set by configurationManager. The private method ConvertKeyToPath helps to find a unique filename based on the url. This example uses binary formatter and a wrapper class that adds an expiration property to any object that needs to be cached. So, the following is the implementation of the wrapper class:
    [Serializable]
    public class CacheItem
    {
        public DateTime ExpiryDate;
        public object Item;
        public CacheItem(object item, DateTime expiryDate)
        {
            ExpiryDate = expiryDate;
            Item = item;
        }
    }
That’s all. When you run this you will notice that you will get an error if the Cache folder does not exist in the root.
To test the caching try to put the next code in the Default.aspx
    <%@ OutputCache Duration=”10″ VaryByParam=”None” %>

IIS Url Forwarding

One easy way of forwarding can be stablished from within the Web.config in the system.web section:

<urlmappings enabled="true"><blockquote><p><urlmappings enabled="true">
<add mappedurl="~/default.aspx?parm1=1" url="~/home">
<add mappedurl="~/Products.aspx" url="~/products">
<add mappedurl="~/Product.aspx?id=1" url="~/product">
</urlmappings>

Catch the Session Expired case

The project I am working on requires to show a nice custom error page to the user when the session is expired. I searched a bit on the internet and came up with a good solution in forums.asp.net which I worked it out as follows:

As mentioned in the forum you need to implement an¬†IHttpModule¬†and redirect the context’e¬†AcquireRequestState¬†event to your method which goes this:
        void Application_AcquireRequestState(object sender, EventArgs e)
{
if (HttpContext.Current.Session != null && HttpContext.Current.Session.IsNewSession)
{
if (HttpContext.Current.Request.Cookies != null && HttpContext.Current.Request.Cookies["ASP.NET_SessionId"] != null)
{
HttpContext.Current.Session.Clear();
HttpContext.Current.Response.Redirect(
"/Errors/SessionTimeout.aspx", true);
}
}
}

}

As you can see I clear the session before redirecting to the SessionTimeout.aspx. The reason for that is that I want the user have a choice of going to login screen by providing a link in the SessionTimeout.aspx. And the login requires a session state unlike the SessionTimeout.aspx that has the attribute EnableSessionState="False" 

URL properties of Request to ASP.NET

The following attributes are some of the usefull properties of the URL object. I use the following url to send my request to localhost:


http://localhost/Test/Asghar.aspx?test=fine

And I get the following results:

AbsolutePath = /Test/Asghar.aspx
AbsoluteUri = http://localhost/Test/Asghar.aspx?test=fine
Authority = localhost
DnsSafeHost = localhost
Fragment =
Host = localhost
HostNameType= Dns
IsAbsoluteUri = True
IsFile = False
LocalPath = /Test/Asghar.aspx
OriginalString = http://localhost:80/Test/Asghar.aspx?test=fine
PathAndQuery = /Test/Asghar.aspx?test=fine
Port = 80
Query = ?test=fine
Scheme = http
UserEscaped = False
UserInfo =

And the code to do it is as followes:

    AbsolutePath = <%= System.Web.HttpContext.Current.Request.Url.AbsolutePath %><br />
AbsoluteUri = <%= System.Web.HttpContext.Current.Request.Url.AbsoluteUri %><br />
Authority = <%= System.Web.HttpContext.Current.Request.Url.Authority %><br />
DnsSafeHost = <%= System.Web.HttpContext.Current.Request.Url.DnsSafeHost %><br />
Fragment = <%= System.Web.HttpContext.Current.Request.Url.Fragment %><br />
Host = <%= System.Web.HttpContext.Current.Request.Url.Host %><br />
HostNameType= <%= System.Web.HttpContext.Current.Request.Url.HostNameType.ToString() %><br />
IsAbsoluteUri = <%= System.Web.HttpContext.Current.Request.Url.IsAbsoluteUri %><br />
IsFile = <%= System.Web.HttpContext.Current.Request.Url.IsFile %><br />
LocalPath = <%= System.Web.HttpContext.Current.Request.Url.LocalPath %><br />
OriginalString = <%= System.Web.HttpContext.Current.Request.Url.OriginalString %><br />
PathAndQuery = <%= System.Web.HttpContext.Current.Request.Url.PathAndQuery %><br />
Port = <%= System.Web.HttpContext.Current.Request.Url.Port %><br />
Query = <%= System.Web.HttpContext.Current.Request.Url.Query %><br />
Scheme = <%= System.Web.HttpContext.Current.Request.Url.Scheme %><br />
UserEscaped = <%= System.Web.HttpContext.Current.Request.Url.UserEscaped %><br />
UserInfo = <%= System.Web.HttpContext.Current.Request.Url.UserInfo %><br />
<hr />

Distinguish Design time in ASP.NET

From MSDN site:

The easiest method to check if the control is at design mode is simply to check the current HttpContext. If this variable returns Nothing, then the control is in design mode.

Private Function IsDesignMode() As Boolean
Try

If HttpContext.Current Is Nothing Then

mboolDesignMode
= True

Else

mboolDesignMode
= False

End If

Return
mboolDesignMode

Catch
Throw

End Try

End Function