Chapter 4. Leveraging the .NET Framework

In This Chapter

  • Working with the .NET Framework

  • Dealing with requests

  • Using ASP.NET security

  • Using site maps

  • Managing files and cookies

  • Tracing and debugging

Let's be clear about ASP.NET and the .NET Framework. They are different. ASP.NET has a dependency on the .NET Framework, but it is really defined as the collection of controls that are in Chapter 3, plus others. The .NET Framework brings a different set of tools.

The controls that are in ASP.NET are user experience focused — they focus on the way the user views the application. The tools that are in the Framework are transport focused — focused on passing information back and forth between client and the server. If you look at the System.Web namespace (which is where most of these bits are stored), you'll quickly see that most of the classes within start with "Http." There is a reason for that: HTTP is the transport protocol.

This is important because manipulating the information that goes back and forth between the client and the server is the first and best way to do anything off-trail in a Web application. Whenever the default condition of the control or the server or the client isn't exactly what you need, the first place you turn is the classes of the System.Web namespace.

Controls in ASP.NET and controls in the .NET Framework overlap quite a bit, but each group is distinct. You should always keep in mind which tools are at your disposal and how best to use them.

Surfing Web Streams

At its core, ASP.NET is about the sending and receiving of streams of text. The client sends requests in, and the server sends HTML out. Sometimes our application is the client, and sometimes it is the server. Classes in the System.Web namespace handle all these cases.

Note

Note that these solutions, along with many others within the System.Web namespace, aren't exclusively related to ASP.NET Web sites. Other applications can sometimes make use of the goodness provided by System.Web, so consider carefully!

Intercepting the request

In a sudden fit of reasonable naming, Microsoft decided to name the object that has all the data from the client request: "Request." That person has since been fired.

There are a few different ways to look at a request, but we are concerned here with the current request, or the context request. Fortunately this is the default request in the ASP.NET space. You can "intercept" that request with ASP.NET. But let's back up.

Digging up the request data

Every time a user types in a URL or clicks a button, a request is issued. Requests are processed by the server, and you can access them through the current context.

Note

Don't let the language fool you: All I mean by "current context" is the object set that ASP.NET gives you just for being there when the request is processed. In short, the class is called HttpRequest, and ASP.NET makes an instance and calls it Request. It is an instance of the HttpRequest class and is the HttpContext.Request property.

To create an environment to show you what I mean, set up a project with these steps:

  1. Create a new ASP.NET project.

    I called mine Chapter 4.

  2. Add a page called Request.aspx.

  3. Right-click on the new Request.aspx page and set it as the start page.

  4. Drag a button control onto the page in the designer.

  5. Double-click the new button to get the Click event handler.

  6. In the new Button1_Click method, add some code to flip through the Request object.

    protected void Button1_Click(object sender, EventArgs e)
    {
        foreach (String requestInfo in Request.Form)
        {
            Debug.WriteLine(requestInfo);
        }
    }

    What we are doing here is just looking at the context returned by the request. It is just somewhere for us to put the debugger, really.

  7. Right-click on the Debug statement and resolve the System.Diagnostics reference.

  8. In Page_Load, add more or less the same code, so we can see the difference between the original request and the postback.

    Notice the check for the postback statement.

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!(Page.IsPostBack))
        {
            foreach (String requestInfo in Request.Form)
            {
                Debug.WriteLine(requestInfo);
            }
        }
    }
  9. Put breakpoints next to the two foreach statements.

  10. Press <F5> to run the application.

When you run, before the Web application appears, the application breaks on the foreach in the Page_Load method. Put your mouse over the Request object there, and you'll see something like Figure 4-1. Notice that the ContentLength is 0 and the HttpMethod is GET. This is an initial URL-based request for a page.

Now press <F10> until the Page_Load event is over the Visual Studio returns control to your Web browser. Notice that the Debug.WriteLine statement is never called. Why? ContentLength is 0. There is no Form. (Kinda like "There is no spoon.") The content is empty, because it is an initial request. The Form collection is empty. Nothing is there except the basic request information.

Press the button when the browser comes back. Visual Studio breaks again on the other foreach statement in the Button1_Click method. Put your mouse over the Request object again. Note the Form collection, shown in Figure 4-2. There are three items in it, the two hidden fields added by ASP. NET and the button that we added, Button1.

The initial request.

Figure 4-1. The initial request.

The form request.

Figure 4-2. The form request.

This is the second type of request — the POST. It is in response to a form. This is where a lot useful information can be found.

Using information from requests

The request is a place that you can collect information about the user's needs, and use it. Especially on postback (though not just then), there is a lot of information that you can use, including the following:

  • The content of the request. The type (ContentType) of the request tells us if it has data or just text. I can learn if it is ISO or Unicode (ContentEncoding). I also know how long the request is (ContentLength).

  • The text of the request itself. For instance, I have all the text of the URL (Url) and the request before this one (UrlReferrer). If I just want the que-rystring (the part after the question mark), I can get it with QueryString.

  • Whether the user is logged into a domain (IsAuthenticated) and if so, who the user is (LogonUserIdentity).

  • Access to the Cookies collection (Cookies).

When you have access to the request, you can make decisions about content. If you need to know about their login information, you have it. Check a cookie — no problem. Confirm the querystring. Whatever needs to be done. You get information to the business layer, get what you need back, compose the new page of controls, and then need to modify the response. That's when you need the HttpResponse class.

Altering content sent to clients

ASP.NET and IIS do a good job of setting up markup for a client browser. You can even tell Visual Studio that you are targeting a certain browser (say, in a workgroup environment), and it will tweak the HTML it sends to the client to make it closer to the exact implementation of the HTML standard that browser supports.

You sometimes need to change the way the page is rendered on the client. More often, you need to change the way the metadata about the page is handed to the client. An HTML page has a lot more than the values you see on the screen. HttpResponse helps you to manage what you can and can't see.

Sending the user somewhere else

One of the most common uses of the Response object is to send the user somewhere else entirely. If you look at the request headers and realize (for instance) that the user isn't logged into a Windows domain account (using HttpRequest.IsAuthenticated), you can redirect the user to a login screen:

If(!Request.IsAuthenticated){
  Response.Redirect("login.aspx");
}

On the other hand, sometimes you need to tell the user that this isn't the right place to be at all. Especially useful if you have to change the structure of your Web site, RedirectPermanently tells search engine spiders "nope, this isn't here anymore. Go over there and change your index."

For instance, say you have a product line that was moved to a whole new URL. You could check the QueryString for the product line statement, and if you find it, redirect the link permanently.

If(Request.Querystring("ProductLineId")==4){
  Response.RedirectPermanently("http://newproduct.com")
}

Changing the request or response directly

Sometimes you just want to change things directly.

Response.Write, Response.WriteFile, and Response.WriteSubstitution all are designed to allow you to do just that. In the old days (before debugging), we had to use Response.Write to put errors into the HTML that we could view with ViewSource. If you are using MVC, there is still a place for that.

In today's development, Response.Write is mostly used to do something that you want to do on every page that meets a certain criteria, no matter who coded it. You can add it to the code-behind of the master page (more on that in Chapter 5) and know that the output stream of the site will be altered directly.

Securing with ASP.NET

Book III covers security, and there is a whole book on security that you should read — Code Complete by Steve McConnell. I'm only covering security in this chapter because the AspNetHostingPermission is in the System.Web namespace, and I want to give it a little airtime.

ASP.NET security is a complex topic. Because visitors to a Web site are anonymous, bypassing the Windows security system occurs often. That bypassing — called impersonation — allows IIS to do things on behalf of the user even if the user isn't really known to Windows. Remember, just because you have put in a username and password doesn't mean that Windows is okay with your credentials.

Note

So ASP.NET Web sites are run most often with whatever permissions the IIS service is running with. Under normal conditions, this service is a fairly restricted account called Network Service. The fact is, however, that you really have no idea what the permissions will be because a hosting company most likely will be managing that. Your users can have administrative permissions for all you know.

You can purposefully restrict the permissions of your users in your code. This prevents someone from finding a way through your code logic to call a method you don't want them to be able to call.

Believe me, it happens.

The general idea is to trust no one, which I harp on in the Security chapter in Book III. You want to pretend that most people who are going to run your code are the enemy. I realize that isn't any fun. But it is necessary today. There are pre-built scripts to break into Web pages — there doesn't even have to be a real person there to bust in.

The MSDN documentation states this:

"It is recommended that you set the level attribute of the trust configuration element to High for sites that are trusted. For sites that are not trusted, such as a Web server that hosts sites that run code from an external customer, it is recommended that you set the level attribute of the trust configuration element to Medium."

For the record, sites without a permission level set default to Full, which is obviously higher than either of those.

Changing trusts

To change the trust of a class, you want to decorate the class in the ASP.NET application with the hosting permission, like this:

using System;
using System.Web;
using System.Security.Permissions;

[AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLe
    vel.High)]
public class ApplicationClass
{
   //My Code is here.
}

So this code uses the High level. The others are None, Minimal, Low, Medium, and Unrestricted. Table 4-1 shows the general breakdown.

Table 4-1. ASP.NET Permission Levels

AspNetHostingPermissionLevel

Description

None

Can't get anywhere. Don't ask.

Minimal

The code can execute but it can't DO anything.

Low

You have read-only access to some restricted information, like the Request itself. Very rough.

Medium

More or less can look at anything — database, network, whatever. No write access.

High

Read/Write for any managed code, but can't run unmanaged (native) code.

Unrestricted

Full run of the box.

Fixing problems

The most common problem is the use of None or Minimal as a permission level. I don't even know why Microsoft offers these choices. If you set the class to Minimal, nothing can get to anything so far as I have been able to figure out. Stick to High for trusted environments, and Medium for shared servers.

Navigating with Site Maps

A site map is an XML file that assists Web design tools in formulating dynamic navigation. There has been a more interesting use for them recently, however. They are used by search engines for indexing your site better.

Note

Webmasters.bing.com is a new Microsoft product that makes your suite more available to users of Bing. It uses the site map file to make your site more indexable. Check it out!

Search Engine Optimization is a big topic these days, and sitemaps can make your life a lot easier. Search engines have set up readers that check for the site map, and then use it to walk the file structure and make sure the links to the site are accurate.

Adding a site map

To make a site map, follow these steps:

  1. Right-click on the project and select Add New ... Item.

  2. Select Site Map from the context menu, as shown in Figure 4-3.

    Adding a site map.

    Figure 4-3. Adding a site map.

  3. Accept the default name, Web.SiteMap.

    Visual Studio builds a template site map. Here is the default code:

    <?xml version="1.0" encoding="utf-8" ?>
    <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
        <siteMapNode url="" title="" description="">
            <siteMapNode url="" title="" description="" />
            <siteMapNode url="" title="" description="" />
        </siteMapNode>
    </siteMap>

    The template file shows you a guideline for adding the navigation of a site. If you have a number of files inside an About section, you make a SiteMapNode of About, and then inside it (before the closing </sitemap-node>), you add the pages in that section, like Contact Us or Our Story.

    For our example, though, there are no sectional divisions to the site. There are in Chapter 5. In this current case, we make it flat and play with that.

  4. Set up the site map for our three-page, flat structure, like this:

    <?xml version="1.0" encoding="utf-8" ?>
    <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
      <siteMapNode>
        <siteMapNode url="" title="" description="" />
        <siteMapNode url="" title="" description="" />
        <siteMapNode url="" title="" description="" />
      </siteMapNode>
    </siteMap>

    Note that the SiteMapNode must be two layers deep, by specification.

  5. Add in the three page names and URLs, like this:

    <?xml version="1.0" encoding="utf-8" ?>
    <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
      <siteMapNode>
        <siteMapNode url="Cookies.aspx" title="Cookies" description="Learn
        all about Cookies" />
        <siteMapNode url="Default.aspx" title="Home" description="The main
        page" />
        <siteMapNode url="Request.aspx" title="Request" description="Coding
        for the Request" />
      </siteMapNode>
    </siteMap>

Navigating a site with SiteMap

With the web.sitemap file installed safely in the application, IIS now has a SiteMap collection that includes the XML file we made right there in memory. If you need to figure out what other pages are related to the page the user is viewing, you can do that. Follow these steps:

  1. Open up the Default.aspx page in the current project.

  2. Drag an empty ListBox control into the page.

  3. Go to Code View.

  4. Loop through the items in the SiteMap collection, and add items to the listbox.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    
    namespace Chapter_4
    {
        public partial class _Default : System.Web.UI.Page
        {
            protected void Page_Load(object sender, EventArgs e)
            {
                // Reference the parent node to keep the object model happy.
                string baseNode = SiteMap.CurrentNode.Title;
    
                // Check to make sure there are subpages.
                if (SiteMap.CurrentNode.HasChildNodes)
                {
                    foreach (SiteMapNode sitemapKids in SiteMap.CurrentNode.
        ChildNodes)
                    {
                        // Put the node name in the listbox.
                        ListBox1.Items.Add( new ListItem(sitemapKids.Title));
                    }
                }
            }
        }
    }

If you get interested, put a debugger in the loop, and check out that SiteMap object. It's a nice collection of the pages that have been referenced in the Web.sitemap file. Though this technology won't cure every navigational problem on every site, it is a nice, underused part of System.Web.

Managing Files

The forms collection contains all the data being sent back from the client. That includes any file that is being uploaded, using the multipart-data format.

ASP.NET uses a two-part plan to get file data to you. Keep in mind, you can do this all by hand, but this all goes back to the idea behind the ASP.NET model. Controls on the front end, System.Web stuff on the back end.

Let's start with the design view. All the user must do is select a local file and the browser handles encoding the file and sending it up to the server. The file encoding standard is built into the protocol, like HTML itself. Follow these steps to set up the upload:

  1. Create a new Web form in our Chapter 4 project called FileUpload.aspx.

  2. Drag a FileUpload control from the Toolbox onto the design surface.

  3. Drag a button onto the surface.

    The button on the FileUpload control is just for selecting an item on your file system. We still need to postback the form.

    That gives us a good start. The FileUpload has a text box and a button that gives you access to the local file system. The button provides a submit function.

    All the magic happens on the back end for this example. The browser handles the file upload for us. The server is our responsibility.

  4. Double-click the submit button on the design surface to get the Button1_Click event handler.

  5. Set up a path to save the file.

    protected void Button1_Click(object sender, EventArgs e)
    {
        String localPath = Server.MapPath("∼/UploadedFiles/");
    }
  6. Add some code to the handler to check if there is a file in the form.The new code is in bold.

    protected void Button1_Click(object sender, EventArgs e)
    {
        String localPath = Server.MapPath("∼/UploadedFiles/");
        if (FileUpload1.HasFile)
        {
        }
    }
  7. In the body of the if statement, have the FileUpload control help us save the file.

    New code is in bold.

    protected void Button1_Click(object sender, EventArgs e)
    {
        String localPath = Server.MapPath("~/UploadedFiles/");
        if (FileUpload1.HasFile)
        {
            try
            {
                FileUpload1.PostedFile.SaveAs(localPath + FileUpload1.
        FileName);
            }
            catch (Exception ex)
            {
                Response.Write(ex.Message);
            }
        }
    }

    This is a nice feature. In the old days, we needed to wrangle the bytes themselves. Uphill both ways, in the snow.

To build a comprehensive file upload function, you need a little more stringent programming practices. (Like my error handling, for instance?) The idea here, though, is that there is a FileUpload control that gives us significant back-end control.

While we are here, take a look at the HttpFileCollection, which is the System.Web class that gives the FileUpload control its power. All the properties of the FileUpload control, like the file's bits, name, and all that, are in the HttpFileCollection. Check it out if you need more fine-grained control than the FileUpload control gives you.

Baking Cookies

Cookies are the only thing that a Web site can directly save on a client machine without the client's direct permission. It still requires the indirect permission, because a user can disable cookies, but passively a cookie can be saved without actually asking the user, "Is it okay if I save this?"

Cookies are useful for state management. If you know the ID of the client's shopping cart, for instance, you can save that ID in a cookie, and even if the client goes away for a week and comes back, you still will be able to go back to the business layer and look up the information.

Cookies are stored in a collection — actually a dictionary — of keys and values. If you know what you are looking for, you can query the collection for the value you want; if you don't, you can loop through the collection until you find it.

The cookies collection is managed using the Request and Response object — based on the HttpRequest and HttpResponse classes we cover earlier in this chapter.

Coding for client-side storage

To get a cookie, you have to first leave a cookie. In part of the response to a client — say, after they log in — you want to tell the platform to save information in the Cookies collection.

Note

Remember that cookies are plain text — anyone can read them. Never store something that can be used by someone wearing a proverbial black hat. I almost always create a System.Guid (a globally unique identifier) and use that to track information that I then persist somewhere in my back-end data store.

What is even better is to store information about the session itself and have an understanding that the information will be removed from the database after some time limit. This prevents the impersonation of the user by someone who intercepts the cookie. There are a lot of options, but check out the best practices offered by Microsoft and other organizations before setting up your cookie strategy.

If I want to store information about the session in the cookie, and tell the database that my customer is using that session, I generate a Guid and save it in both places. Then I can retrieve the cookie at the next request, compare it to the available Guids in the user collection, and find the user in question.

Here is an example of setting the cookie based on session information:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Chapter_4
{
    public partial class Cookies : System.Web.UI.Page
    {
        WebUser currentUser = new WebUser();
        List<WebUser> usersInDatabase = new List<WebUser>();
        protected void Page_Load(object sender, EventArgs e)
        {
if (!Page.IsPostBack)
               {
                   //This is a first request, so we need to set the cookie
                   Guid sessionGuid = new Guid();
                   currentUser.SessionId = sessionGuid;
                   //Tell the database about the new user
                   usersInDatabase.Add(currentUser);
                   //Set the cookie
                   Response.Cookies.Add(new HttpCookie("SessionId", sessionGuid.
       ToString()));
              }
          }
       }
       //This class would be in the library somewhere,
       //not actually in this file.
       public class WebUser
       {
           public Guid SessionId { get; set; }
       }
   }

Wrangling cookies on the server

So now the user has the cookie, and the next request comes in. I need to grab the cookie from the collection and search my known user base to get the information that I need. The boldface in the code below indicates the code added:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Chapter_4
{
    public partial class Cookies : System.Web.UI.Page
    {
        WebUser currentUser = new WebUser();
        List<WebUser> usersInDatabase = new List<WebUser>();
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                //This is a first request, so we need to set the cookie
                Guid sessionGuid = new Guid();
                currentUser.SessionId = sessionGuid;
                //Tell the database about the new user
                usersInDatabase.Add(currentUser);
                //Set the cookie
                Response.Cookies.Add(new HttpCookie("SessionId", sessionGuid.
    ToString()));
           }
           else
           {
//The is a postback so we need to get the cookie
               string cookieSession = Request.Cookies.Get("SessionId").Value.
   ToString();
               Guid sessionGuid = new Guid(cookieSession);
               var returningUser = from u in usersInDatabase
                  where u.SessionId.ToString() == sessionGuid.ToString()
                  select u;
               foreach (var user in returningUser)
               {
                   //Better only be one
                   currentUser = user;
               }
       }
   }
   //This class would be in the library somewhere,
   //not actually in this file.
   public class WebUser
   {
       public Guid SessionId { get; set; }
   }
}

How ASP.NET manages cookies for you

A lot of the stuff we use to store in a cookie is managed by the ASP.NET engine. Session state, the most common example (discussed earlier), is now handled by the ViewState object. You can store information in the ViewState like you would in a variable, and the information is encoded for you and kept in a special field in the markup.

ViewState has its problems, and a lot of people don't like to use it. In fact, many ASP.NET applications in enterprises have turned it off to save bandwidth and prevent poor coding practices. In these cases, a return to cookies is your best bet to maintain a constant communication with the user.

Tracing with TraceContext

The TraceContext class provides all the detailed server processing information about a Web page in ASP.NET. It is exposed in the code-behind as the Trace object and allows you to write messages to the trace log and the screen when certain things happen.

The benefit is that the Trace code runs only when you have tracing turned on. This gives programmers the option to leave tracing code in a working application without it impacting the performance or functionality.

This allows you to think about debugging while you code. When I build a Web application, I just assume that it will break. Fact is, it probably will. Programming is hard, and Web programming is harder. It is likely that either you or the environment will have a fault at some point.

Why not make it easy on yourself later on down the road?

If you decide which parts of the application are most likely to have problems while you code them, you can insert trace messages as you go. It seems like a defeatist attitude, but really I think it is a realist attitude. Even if it is just caused by bad data in two years, you are probably going to spend some time debugging.

To use tracing, you can alter the @Page statement at the top of each page in the application, or you can alter the Web.config file — which is what we are gonna do. If you want to set up the whole site for tracing, just add the bold code below to the Web.Config:

<configuration>
  <system.web>
    <trace enabled="true" requestLimit="40" localOnly="false" />
  </system.web>
</configuration>

Follow these steps to alter the Web.config file for tracing:

  1. Create a new page in the project for tracing.

    I called it Tracing.aspx.

  2. Add the Trace statement to the @page directive of the new Tracing. aspx page.

    <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Tracing.aspx.
        cs" Inherits="Chapter_4.Tracing" Trace="true"%>
  3. In the code-behind, add code to the Page_Load event handler to fakean exception and catch it, and then write to the Trace.

    This is just to see how Trace works. Book I covers Exception handling in more detail.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    
    namespace Chapter_4
    {
        public partial class Tracing : System.Web.UI.Page
        {
            protected void Page_Load(object sender, EventArgs e)
            {
                try
                {
                    throw new ApplicationException("This is the fake
        exception.");
                }
    catch (ApplicationException ex)
             {
                 Trace.Warn(ex.Message);
             }
          }
       }
    }
  4. Press <F5> to run the application.

    The page will look nothing like you expect. A ton of information is dumped on the page, Response.Write style, including our Trace message (check out Figure 4-4).

    An example of ASP.NET tracing.

    Figure 4-4. An example of ASP.NET tracing.

You don't want to show the user all of this, of course, but if you get word of a problem with the application, wouldn't it be nice to be able to turn on the trace for the page in question? You could give it a little input, and then see how the server processing details respond using the trace.

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

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