Beginning Visual Basic(2012)
Appendix A
Exercise Answers
Chapter 1
Exercise 1 Solution
The markup of a page in Visual Studio contains the raw and unprocessed source for the page, including the HTML, ASP.NET Server Controls, and programming code. The web server then processes the page and sends out the final HTML to the browser. In the browser this HTML is then used to render the user interface.
Exercise 2 Solution
The easiest way to store HTML fragments that you use often is to select them in the Document Window and then drag them to a free space on the Toolbox. When the item is added, you can rename it to a more meaningful name. Now you can simply double-click an item or drag it from the Toolbox into your page whenever you need it.
Exercise 3 Solution
You have a number of ways to reset part of the customization changes you may have made, including:
· Resetting the window layout by choosing Window insert symbol Reset Window Layout.
· Resetting the Toolbox by right-clicking it and choosing Reset Toolbox.
· Resetting all settings of Visual Studio using Tools insert symbol Settings insert symbol Import and Export Settings or Tools insert symbol Import and Export Settings, depending on your version of Visual Studio.
Exercise 4 Solution
To change the property of a control on a page, you can click the control in Design or Markup View and then use the Properties Grid (which you can bring up by pressing F4) to change the value of the property. Alternatively, you can change the property directly in Markup View.
Chapter 2
Exercise 1 Solution
A number of files fall in the Web Files category, including .aspx files (Web Forms that end upas pages in the web browser), .html files (that contain static HTML for your site), .css files (that contain cascading style sheet information), and .config files (that contain configuration information for the website). Refer to the table with the different file types in Chapter 2 for a complete listof files.
Exercise 2 Solution
When you want to make a piece of text both bold and italicized, you need to select the text and then click the Bold button on the Formatting toolbar. Next you need to click the Italic button. The final HTML code in the page should look like this:
<strong><em>Welcome to Planet Wrox</em></strong>
You may also come across <b> and <i> elements, which have the same effect. However, these are now considered outdated and you're encouraged to use <strong> and <em> instead.
Exercise 3 Solution
The first way is using the Solution Explorer. Right-click your site, choose Add insert symbol Existing Item, and then browse for the item(s) you want to add.
Secondly, you can drag and drop files from File Explorer or Windows Explorer or from yourdesktop directly into a VS website. As a third alternative, you could put the files directly in the site's folder using Windows Explorer. For example, files you add to the folder C:\BegASPNET\Site become part of your website automatically. If you don't see the new files appear in VS directly, click the Refresh icon on the Solution Explorer toolbar.
Exercise 4 Solution
In VS, you have three different views on your code: Design View, Markup View (also referred to as Source View or Code View), and Split View. The first gives you an impression of how your web page is going to look in the browser, and the second view shows you the raw markup. Split View enables you to see both views at the same time. VS also has different views for other files. For example, programming code for an ASPX page is generally referred to as the Code Behind view or simply the Code Behind.
Chapter 3
Exercise 1 Solution
The biggest benefit of an external style sheet is the fact that it can be applied to the entire site. Simply by changing a single rule in that file, you can influence all pages in your site that make use of that rule. With embedded or inline styles, you need to change all the files in your site manually. External style sheets also make it much easier to reuse a certain style with many different elements in your site. Simply create a Class or an ID selector and reuse that wherever you see fit.
Exercise 2 Solution
The rule can look like this:
h1
{
font-family: Arial;
color: Blue;
font-size: 18px;
border-top: 1px solid Blue;
border-left: 1px solid Blue;
}
Note another shorthand version of the border property. This one looks similar to the border property you saw earlier in this chapter that enabled you to set the size, style, and color of the border at once. In this rule, border-top and border-left are used to change the left and top borders only; the other two directions, the bottom border and right border, are not affected by this rule.
Exercise 3 Solution
The second declaration is more reusable in your site because it denotes a Class selector as opposed to an ID selector. The latter can only be used once in a single page by an element that has a matching id attribute: MainContent in this example. The Class selector BoxWithBorders, on the other hand, can be used by multiple elements in a single page, because you are allowed to apply an identical class attribute to multiple elements in a page.
Exercise 4 Solution
VS lets you attach a style sheet in the following ways:
· Type in the required <link> element in the <head> of the page in Markup View directly.
· Drag a CSS file from the Solution Explorer into the <head> section of a page in Markup View.
· Drag a CSS file from the Solution Explorer and drop it onto the page in Design View.
· Use the main menu Format insert symbol Attach Style Sheet and then browse to your CSS file.
· Use the Manage Styles or Apply Styles windows and click the Attach Style Sheet link.
Chapter 4
Exercise 1 Solution
The mechanism that enables controls to maintain their state is called View State.
Exercise 2 Solution
The ASP.NET run time stores the values for the controls in a hidden field called __VIEWSTATE. This hidden field is sent with each postback to the server, where it's unpacked and then used to repopulate the controls in the page with their previous values.
Exercise 3 Solution
The DropDownList only allows a user to make a single selection whereas the ListBox allows for multiple selections. In addition, the DropDownList only shows one item in the list when it's not expanded, while the ListBox is capable of displaying multiple items simultaneously.
Exercise 4 Solution
In order to have a CheckBox control submit back to the server when you select or clear it in the browser, you need to set the AutoPostBack property to True:
<asp:CheckBox ID="CheckBox1" runat="server" AutoPostBack="True"/>
Exercise 5 Solution
Many of the ASP.NET Server Controls let you change colors using properties like BackColor and ForeColor. Additionally, you can use BorderColor, BorderStyle, and BorderWidth to change the border around a control in the browser. Finally, to affect the size of the control, you need to set itsHeight and Width properties.
Exercise 6 Solution
Instead of setting individual styles, you're much better off setting the CssClass property that points to a rule set. This way, your pages become easier to maintain, as style-related information is stored in a single place: in the style sheet. At the same time, your page loads faster as not all the style information is sent for each control on each request. Instead, the browser reads in the style sheet once and keeps a locally cached copy.
Chapter 5
Exercise 1 Solution
Both the Byte and the SByte data types are designed to hold small, numeric values. Both of them take up exactly the same amount of computer memory, so you're probably best off using the Byte data type. Because it doesn't allow you to store negative numbers, it's clear from the start that it can only contain a number between 0 and 255. However, it's much better not to store someone's age, but the date of birth instead. That way, you can always extract the age from the date of birth by comparing it with today's date. Because the date of birth is a fixed point in time, it will always reflect someone's age correctly without the need to annually update it.
Exercise 2 Solution
This piece of code toggles the visibility of the DeleteButton control. It uses both the assignment operator and the negation operator. First, the negation operator is applied to the current value of Visible. When that value is currently True, the Not (! in C#) operator turns it into False and vice versa. This result is then assigned back to the Visible property. So, where the button was previously hidden, it is now visible. Where it was visible before, it's now made invisible.
Exercise 3 Solution
To create a specialized version of Person, you need to create a second class that inherits the Person class and extend its behavior by adding the StudentId property.
VB.NET
Public Class Student
Inherits Person
Public Property StudentId As String
End Class
C#
public class Student : Person
{
public string StudentId { get; set; }
}
Chapter 6
Exercise 1 Solution
The ContentPlaceHolder element should be placed in the master page. It defines a region that content pages can fill in. The Content control should be placed in a content page that is based on the master page. It is used to supply the content for the ContentPlaceHolder element that it is connected to.
Exercise 2 Solution
To link a Content control to its ContentPlaceHolder in the master page, you need to set the ContentPlaceHolderID:
<asp:Content ID="Content1" ContentPlaceHolderID="IdOfContentPlaceHolder"
runat="Server">
</asp:Content>
Exercise 3 Solution
You have a few ways to do this. First, you can create a named skin with a different CSS class in the same skin file:
<asp:Button runat="server" SkinID="RedButton" CssClass="RedButton"/>
You then hook up the control you want to change to this named skin using the SkinID attribute:
<asp:Button ID="Button1" runat="server" Text="Button" SkinID="RedButton"/>
Alternatively, you can disable theming on the Button control and give it a different CSS class directly in the ASPX page:
<asp:Button ID="Button1" runat="server" EnableTheming="False"
CssClass="RedButton" Text="Button"/>
In both solutions, you need a CSS class that sets the background color:
.RedButton
{
background-color: Red;
}
Exercise 4 Solution
A StyleSheetTheme is applied early in the page's life cycle. This gives controls in the ASPX page the opportunity to override settings they initially got from the StyleSheetTheme. This means that the StyleSheetTheme just suggests the look and feel of controls, giving the individual controls the ability to override that look.
A Theme, on the other hand, overrides any settings applied by the controls. This enables you as a page developer to enforce the look and feel of controls in your site with the settings from the theme. If you still need to change individual controls, you can disable theming by setting EnableThemingto False.
Exercise 5 Solution
You have three ways to set the Theme property in an ASP.NET website. The first option is to set the property directly in the @ Page directive so it applies to that page only:
<%@ Page Language="C#" ...... Theme="Monochrome" %>
To apply a theme to all pages in your site, you set the theme attribute of the <pages> element in the Web.config file:
<pages theme="Monochrome" …
The final option you have is to set the theme programmatically. You have to do this in the PreInit event of the Page class, which you can handle in individual pages in your site or at a central location using the BasePage class as you did in Chapter 6.
Exercise 6 Solution
A base page enables you to centralize behavior for all the pages in your site. Instead of recoding the same functionality over and over again in every page, you move this code to the base page so all ASPX pages can use it. When you implement the theme switcher, all you have to do is write some code in the central BasePage class. All pages in your site that inherit from this BasePage class then automatically set the selected theme, without the need for any additional code.
Chapter 7
Exercise 1 Solution
To change the background color of items in the TreeView control you can use the NodeStyle element as follows:
<asp:TreeView ID="TreeView1" runat="server"
DataSourceID="SiteMapDataSource1" ShowExpandCollapse="False">
<NodeStyle BackColor="White"/>
…
</asp:TreeView>
Instead of setting the BackColor property (which results in an inline style), you're better off setting the CssClass property:
<NodeStyle CssClass="TreeViewNodeStyle"/>
You then need to create a separate class in your CSS file:
.TreeViewNodeStyle
{
background-color: White;
}
This way, it's easier to manage the styling from a central location.
Exercise 2 Solution
To redirect a user to another page programmatically, you can use Response.Redirect, Response.RedirectPermanent, and Server.Transfer. The first two options send a redirect instruction to the browser and are thus considered client-side redirects. Server.Transfer, on the other hand, takes place at the server, enabling you to serve a different page without affecting the user's address bar.
Exercise 3 Solution
To display a TreeView that doesn't have the ability to expand or collapse nodes, you need to set its ShowExpandCollapse property to False.
Chapter 8
Exercise 1 Solution
A standard property uses a normal backing variable to store its value, whereas a View State property uses the ViewState collection for this. A normal property is reset on each postback, whereas a View State property is able to maintain its value. This advantage of the View State property comes at a cost, however. Storing the value in View State adds to the size of the page, both during the request and the response. A normal property doesn't have this disadvantage. You should carefully consider what you store in View State to minimize the page size.
Exercise 2 Solution
To make the property maintain its state across postbacks, you need to turn it into a View State property. The required code is almost identical to that of the NavigateUrl, but it uses the Direction data type instead of a String. You need to remove the automatic property and replace it with the following code:
VB.NET
Public Property DisplayDirection() As Direction
Get
Dim _displayDirection As Object = ViewState("DisplayDirection")
If _displayDirection IsNot Nothing Then
Return CType(_displayDirection, Direction)
Else
Return Direction.Horizontal ' Not found in View State; return a default value
End If
End Get
Set(Value As Direction)
ViewState("DisplayDirection") = Value
End Set
End Property
C#
public Direction DisplayDirection
{
get
{
object _displayDirection = ViewState["DisplayDirection"];
if (_displayDirection != null)
{
return (Direction)_displayDirection;
}
else
{
return Direction.Horizontal; // Not found in View State;
// return a default value
}
}
set
{
ViewState["DisplayDirection"] = value;
}
}
Exercise 3 Solution
Using a custom data type like the Direction enumeration has two benefits over using numeric or String data types. Because of the way IntelliSense helps you select the right item, you don't have to memorize magic numbers or strings like 0 or 1. Additionally, the compiler helps you check the spelling, so if you type Direction.Vrtical instead of Direction.Vertical, you get an error at development time.
Chapter 9
Exercise 1 Solution
First, you need to write a property in the Code Behind of the user control, similar to the DisplayDirection property you created in the previous chapter for the Banner control. This property could look like this:
VB.NET
Public Property PageDescription As String
C#
public string PageDescription { get; set; }
You then need to modify the control declaration. For example, in Contact.aspx, you can modify the control like this:
<uc1:ContactForm ID="ContactForm" runat="server"
PageDescription="Contact Page"/>
Note that the PageDescription property contains a short description of the containing page.
Obviously, you can put whatever text you see fit in the property to describe the page. Finally, you need to add the PageDescription to the subject or body of the e-mail message. The following code snippet shows you how to extend the subject with the value of this new property:
VB.NET
myMessage.Subject = "Response from web site. Page: " & PageDescription
myMessage.From = New MailAddress("you@example.com", "Sender Name")
C#
myMessage.Subject = "Response from web site. Page: " + PageDescription;
myMessage.From = new MailAddress("you@example.com", "Sender Name");
From now on, this custom page description is added to the subject of the mail message. This solution becomes particularly useful if you have multiple Web Forms using this contact form and you want to find out which page was used to send the information.
Exercise 2 Solution
If you don't inspect the IsValid property, your system is vulnerable to invalid data. Users can disable JavaScript in their browsers and submit invalid data directly into your page. By checking the IsValid property you can tell whether or not it's safe to continue with the submitted data.
Exercise 3 Solution
The From property of the MailMessage class is of type MailAddress, meaning that you can directly assign a single instance of this class to it. Because you can potentially have more than one recipient, the To property is a collection of MailAddress objects, and so you need to use its Add method to add instances of MailAddress to it.
Exercise 4 Solution
To call a client-side validation function, you need to set the ClientValidationFunction property of the CustomValidator like this:
<asp:CustomValidator ID="CustomValidator1" runat="server"
ClientValidationFunction="functionName">*</asp:CustomValidator>
The client function that you need to add to the markup of the page must have the following signature:
function functionName(source, args)
{ … }
The source argument contains a reference to the actual CustomValidator control in the client-side HTML code. The args argument provides context information about the data and enables you to indicate whether or not the data is valid. The names of the arguments don't have to be source andargs; however, when using these names, the client-side function looks as close to its server-side counterpart as possible. Another common naming scheme, used for almost all other event handlers in ASP.NET, is to use sender and e, respectively.
Exercise 5 Solution
To tell the validation mechanism whether the data you checked is valid, you set the IsValid property of the args argument in your custom validation method. This applies to both client- and server-side code. The following snippet shows how this is done in the client-side validation method for theContactForm.ascx control:
if (phoneHome.value != '' || phoneBusiness.value != '')
{
args.IsValid = true;
}
else
{
args.IsValid = false;
}
Chapter 10
Exercise 1 Solution
The ScriptManager control is a required component in almost all Ajax-related operations. It takes care of registering client-side JavaScript files, handles interaction with web services defined in your website, and it's responsible for the partial page updates. You usually place the ScriptManagerdirectly in a content page if you think you need Ajax capabilities on only a handful of pages. However, you can also place the ScriptManager in a master page so it becomes available throughout the entire site.
When you have the ScriptManager in the master page you can use the ScriptManagerProxy to register individual web services or script files on content pages. Because you can have only one ScriptManager in a page, you can't add another one in a content page that uses your master page with theScriptManager. The ScriptManagerProxy serves as a bridge between the content page and the ScriptManager, giving you great flexibility as to where you register your services.
Exercise 2 Solution
You can let your users know a partial page update is in progress by adding an UpdateProgress control to the page. You connect this control to an UpdatePanel using its AssociatedUpdatePanelID. Inside the <ProgressTemplate> you define whatever markup you see fit to inform your useran update is in progress. A typical <ProgressTemplate> contains an animated icon, some text, or both.
Exercise 3 Solution
To create a script-callable service, you first need to add an AJAX-enabled WCF service file to your site using the Add New Item dialog box. The class file that is created for you already has the ServiceContract and AspNetCompatibilityRequirements attributes applied.
VB.NET
<ServiceContract(Namespace:="")>
<AspNetCompatibilityRequirements(
RequirementsMode:=AspNetCompatibilityRequirementsMode.Allowed)>
Public Class NameOfYourService
C#
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(
RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class NameOfYourService
You then need to decorate each method within this class that you want exposed as a web method with the OperationContract attribute:
VB.NET
<OperationContract()>
Public Function NameOfYourMethod(parameters) As DataType
C#
[OperationContract]
public DataType NameOfYourMethod(parameters)
Once you've registered the WCF service in a ScriptManager or ScriptManagerProxy control, you can call the service as follows from your client-side JavaScript:
NameOfYourService.NameOfYourMethod(
parameters, successCallback, errorCallback);
Exercise 4 Solution
To expose and use a page method you need to carry out the following steps:
1. Set EnablePageMethods on the ScriptManager to True.
2. Define a static (Shared in VB.NET) method in your page and apply the WebMethod attribute like this:
VB.NET
<WebMethod()>
Public Shared Function NameOfYourMethod(parameters) As DataType
…
End Function
C#
[WebMethod]
public static DataType NameOfYourMethod(parameters) { … }
3. You call the method in JavaScript through the PageMethods object and define a callback method to handle the result of the method:
PageMethods.NameOfYourMethod(parameters, successCallback, errorCallback);
Chapter 11
Exercise 1 Solution
To accomplish this, you first need to set the ClientIDMode of VerticalPanel to Static in the markup of Banner.ascx. This makes it easier to work with the client IDs of the controls inside the panel:
<asp:Panel ID="VerticalPanel" runat="server" ClientIDMode="Static">
Then you need to add a <span> element just below VerticalPanel with the text Hide Banner. The style attribute changes the mouse cursor into a hand so users understand the text is clickable when they hover over it. You need to add a runat attribute and a server-side ID so you can hide the link in the Code Behind. You also need to give it a class attribute so you can find the element using jQuery. You should end up with this span element:
<span id="HideBanner" style="cursor: pointer;" runat="server"
class="HideBanner">Hide Banner</span>
In a script block below the last panel, you need to add the following code:
<script type="text/javascript">
$(function ()
{
$('.HideBanner').bind('click', function ()
{
$('#VerticalPanel').slideToggle('fast', function ()
{
if ($(this).css('display') == 'block')
{
$('.HideBanner').text('Hide Banner');
}
else
{
$('.HideBanner').text('Show Banner');
}
});
});
});
</script>
This code dynamically binds some code to the click event of the <span> element found by using $('.HideBanner'). Then inside the handler for the click, the code finds the whole panel (#VerticalPanel) and calls slideToggle, which hides items in the matched set when they are visible and shows them when they aren't. The if check then uses the css method on the VerticalPanel element (now referred to with the this keyword) and asks for its display property. When it is block it means the banner is visible, and thus the text must be Hide Banner. Otherwise, the text is set to Show Banner. Finally, you need to hide the link when the banner is in horizontal mode in the banner control's Code Behind:
VB.NET
Case Direction.Horizontal
HorizontalPanel.Visible = True
VerticalPanel.Visible = False
HorizontalLink.HRef = NavigateUrl
HideBanner.Visible = False
C#
case Direction.Horizontal:
HorizontalPanel.Visible = true;
VerticalPanel.Visible = false;
HorizontalLink.HRef = NavigateUrl;
HideBanner.Visible = false;
break;
Exercise 2 Solution
The slideUp method hides elements by slowly decreasing their height. slideDown shows hidden elements instead by doing the reverse: slowly increasing the height of an element until it's fully visible. Both methods accept, among other arguments, a speed parameter that either accepts a fixed value (slow, normal, or fast) or a number specifying the speed of the animation in milliseconds.
Exercise 3 Solution
jQuery's document ready function fires when the page is finished loading in the browser, only during the initial request. The pageLoad method fires both when the page first loads and after an asynchronous postback, for example, when using an UpdatePanel. This difference enables you to choose the desired behavior. Need to fire some code on initial load and after a postback? Choose pageLoad. Otherwise, choose jQuery's document ready function.
Exercise 4 Solution
You use the special _references.js file in the Scripts folder to enable IntelliSense. VS will read this file and parse all JavaScript files that you added with this syntax:
/// <reference path="Path/To/File.js"/>
This way, you get IntelliSense in files where it would otherwise not be supported (such as user controls) or for JavaScript files that you're not directly linking to in your website.
Chapter 12
Exercise 1 Solution
The DELETE statement fails because there is a relationship between the Id of the Genre table and the GenreId of the Review table. As long as this relationship is in effect, you cannot delete genres that still have reviews attached to them. To be able to delete the requested genre, you need to delete the associated reviews first, or assign them to a different genre using an UPDATE statement. Exercise 4 shows you how you can accomplish this.
Exercise 2 Solution
The relationship between the Genre and Review tables is a one-way relationship. The relationship enforces that the GenreId assigned to a review must exist as an Id in the Genre table. At the same time, it blocks you from deleting genres that have reviews attached to them. However, the relationship doesn't stop you from deleting rows from the Review table.
Exercise 3 Solution
To delete reviews with an Id of 100 or less, you need the following SQL statement:
DELETE FROM Review WHERE Id <= 100
Exercise 4 Solution
Before you can delete the genre, you need to reassign the existing reviews to a new genre first. You can do this with the following UPDATE statement:
UPDATE Review SET GenreId = 11 WHERE GenreId = 4
This assigns the GenreId of 11 to all reviews that previously had their GenreId set to 4. This in turn means that the genre with an Id of 4 no longer has any reviews attached to it, so you can now remove it with the following SQL statement:
DELETE FROM Genre WHERE Id = 4
Exercise 5 Solution
To update the name you need to execute an UPDATE statement. To limit the number of affected rows to just the Rock genre, you need to use a WHERE clause. You can use the WHERE clause to filter the rows based on the genre's Id or on its Name. The following SQL statements are functionally equivalent:
UPDATE Genre SET Name = 'Punk Rock' WHERE Id = 7
UPDATE Genre SET Name = 'Punk Rock' WHERE Name = 'Rock'
This code assumes that the current Rock genre has an Id of 7.
Chapter 13
Exercise 1 Solution
The best control for this scenario is the GridView control. It's easy to set up and has built-in support for paging, updating, and deleting of data. Together with a DetailsView control you can offer your users all four CRUD operations. To connect to your database you need to use a SqlDataSourcecontrol. Chapter 14 provides you with alternatives for both the GridView and the SqlDataSource.
Exercise 2 Solution
For a simple, unordered list, you're probably best off using a Repeater control hooked up to a SqlDataSource control. The biggest benefit of the Repeater control is that it emits no HTML code of its own, enabling you to control the final markup. A downside of the control is that it doesn't support editing or deletion of data, which isn't a problem if all you need to do is present the data in a list. Chapter 14 shows you how to use the Repeater control.
Exercise 3 Solution
A BoundField is directly tied to a column in your data source and offers only limited ways to customize its appearance. The TemplateField, on the other hand, gives you full control over the way the field is rendered. As such, it's an ideal field for more complex scenarios—for example, when you want to add validation controls to the page, or if you want to let the user work with a different control, like a DropDownList instead of the default TextBox.
Exercise 4 Solution
You should always store your connection strings in the Web.config file. This file has an element called <connectionStrings> that is designed specifically for storing connection strings. By storing them in Web.config, you make it very easy to find your connection strings and modify them. If you store them at the page level, you have to search through your entire project for the relevant connection strings.
You can access the connection strings using expression binding syntax. For example, to set the connection string in a SqlDataSource, you can use code like this:
ConnectionString="<%$ ConnectionStrings:PlanetWroxConnectionString1 %>"
For this code to work, you need a connection string similar to this in your Web.config file:
<connectionStrings>
<add name="PlanetWroxConnectionString1" connectionString="Data
Source=(localdb)\v11.0;AttachDbFilename=|DataDirectory|\PlanetWrox.mdf;
Integrated Security=True"
providerName="System.Data.SqlClient"/>
</connectionStrings>
Chapter 14
Exercise 1 Solution
To get the 10 most recent reviews from the system, your query needs two important LINQ constructs: first, it needs an Order By (orderby in C#) clause to order the list in descending order. It then needs the Take method to take the first 10 reviews from that result set:
VB.NET
Using myEntities As New PlanetWroxEntities()
Dim recentReviews = (
From myReview In myEntities.Reviews
Order By myReview.CreateDateTime Descending
Select New With {
myReview.Title, myReview.Genre.Name
}
).Take(10)
GridView1.DataSource = recentReviews
GridView1.DataBind()
End Using
C#
using (PlanetWroxEntities myEntities = new PlanetWroxEntities())
{
var recentReviews = (from myReview in myEntities.Reviews
orderby myReview.CreateDateTime descending
select new { myReview.Title, myReview.Genre.Name }).Take(10);
GridView1.DataSource = recentReviews;
GridView1.DataBind();
}
This code also uses the New keyword (new in C#) to create a new, anonymous type that contains only the review's title and the genre's name.
Exercise 2 Solution
The biggest benefit of the ListView control is that it combines the strengths of those other data controls. Just like the GridView control, the ListView control makes it easy to display data in a grid format that users can edit from within the grid. Additionally, the ListView control enables you to insert new rows, behavior that is found in controls like DetailsView and FormView but not in GridView.
Finally, the ListView control gives you full control over the markup that gets sent to the browser, an important feature that only the Repeater control gives you out of the box.
Exercise 3 Solution
First you would need to change the Default.aspx page in the PhotoAlbums folder so it links each thumbnail to a details page and passes the ID of the picture to this new page. In the <ItemTemplate> of the ListView control in Default.aspx, add this HyperLink control around the Image control that was already there:
<asp:HyperLink ID="HyperLink1" runat="server"
NavigateUrl='<%# "PictureDetails.aspx?Id=" + Item.Id.ToString() %>'>
<asp:Image ID="Image1" runat="server" ImageUrl='<%# Item.ImageUrl %>'
ToolTip='<%# Item.ToolTip %>'/>
</asp:HyperLink>
Note that the NavigateUrl is built up from the static text PictureDetails.aspx?Id= and the ID of the picture in the database.
Then create a new page called PictureDetails.aspx and add an Image control in the markup:
<asp:Content ID="Content2" ContentPlaceHolderID="cpMainContent" runat="server">
<asp:Image ID="Image1" runat="server"/>
</asp:Content>
Finally, you need to execute the following LINQ query in the Load event of the page in the Code Behind to set the ImageUrl:
VB.NET
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim pictureId As Integer = Convert.ToInt32(Request.QueryString.Get("Id"))
Using myEntities As New PlanetWroxEntities()
Dim imageUrl As String = (From picture In myEntities.Pictures
Where picture.Id = pictureId
Select picture).Single().ImageUrl
Image1.ImageUrl = imageUrl
End Using
End Sub
C#
protected void Page_Load(object sender, EventArgs e)
{
int pictureId = Convert.ToInt32(Request.QueryString.Get("Id"));
using (PlanetWroxEntities myEntities = new PlanetWroxEntities())
{
string imageUrl = (from picture in myEntities.Pictures
where picture.Id == pictureId
select picture).Single().ImageUrl;
Image1.ImageUrl = imageUrl;
}
}
This code gets the ID of the picture from the query string first and then feeds it to the LINQ query.
The Single method is used to retrieve a single picture from the Picture table whose ImageUrl is then used to display the image in the browser.
Exercise 4 Solution
The best location to delete the image from disk is in the EntityDataSource control's Deleted event that fires after the item has been deleted from the database. Inside a handler for this event you get a reference to the Picture that has been deleted, find its ImageUrl, convert the URL to a physical location, and delete the image. To implement this, follow these steps:
1. Open ManagePhotoAlbum.aspx from the root of the site in Design View, select the EntityDataSource control, open its Properties Grid, and switch to the Events tab.
2. Double-click the Deleted event and in Code Behind add the following code to the handler that VS added for you:
VB.NET
Dim myPicture As Picture = CType(e.Entity, Picture)
Dim fileName As String = Server.MapPath(myPicture.ImageUrl)
System.IO.File.Delete(fileName)
C#
Picture myPicture = e.Entity as Picture;
string fileName = Server.MapPath(myPicture.ImageUrl);
System.IO.File.Delete(fileName);
3. Add ImageUrl to the list of DataKeyNames of the ListView control:
<asp:ListView ID="ListView1" … DataKeyNames="Id,ImageUrl" …>
This code first finds a reference to the Picture using e.Entity. It then grabs its virtual ImageUrl, converts it to a physical path, and then uses the Delete method of the File class to get rid of the file on disk. Because the entire image is constructed from View State and not requested from EF, you need to store the ImageUrl in the DataKeyNames property. This adds it to View State so it roundtrips to the browser and is available in the Deleted event.
Exercise 5 Solution
To display only genres that have at least one review, all you need to do is filter out empty genres using Where with a Count method like this:
VB.NET
Dim genresWithReviews = From genre In myEntities.Genres
Order By genre.Name
Where genre.Reviews.Count() > 0
Select New With {genre.Name, genre.Reviews}
C#
var genresWithReviews = from genre in myEntities.Genres
orderby genre.Name
where genre.Reviews.Count() > 0
select new { genre.Name, genre.Reviews };
Chapter 15
Exercise 1 Solution
The Load event of the Page always fires before user-triggered events such as a Button control's Click.
Exercise 2 Solution
To preselect the correct item in the drop-down list after a user has inserted or edited a review, you need to make two modifications. First, you need to change the Redirect statement in the AddEditReviewHandCoded.aspx page so it includes the ID of the genre:
VB.NET
Response.Redirect(String.Format("Reviews.aspx?GenreId={0}",
GenreList.SelectedValue))
C#
Response.Redirect(string.Format("Reviews.aspx?GenreId={0}",
GenreList.SelectedValue));
Using String.Format makes this code a bit easier to read as opposed to plain string concatenation using the ampersand (&) in VB.NET and the plus (+) in C#.
If you now insert or edit a new review, you'll see that the ID of the genre is passed back to the Reviews.aspx page. On that page, you can use that ID to preselect the correct item in the DropDownList control, which you can accomplish with the following code in the Page_Load method:
VB.NET
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
If Not Page.IsPostBack Then
Dim genreId As String = Request.QueryString.Get("GenreId")
If Not String.IsNullOrEmpty(genreId) Then
DropDownList1.DataBind()
Dim myItem As ListItem = DropDownList1.Items.FindByValue(genreId)
If myItem IsNot Nothing Then
myItem.Selected = True
End If
End If
End If
End Sub
C#
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
string genreId = Request.QueryString.Get("GenreId");
if (!string.IsNullOrEmpty(genreId))
{
DropDownList1.DataBind();
ListItem myItem = DropDownList1.Items.FindByValue(genreId);
if (myItem != null)
{
myItem.Selected = true;
}
}
}
}
Only when the page loads from a new request (and not from a postback) does this code fire. The code then tries to find a GenreId in the query string. If it can find it, it tries to find an item with that requested value in the DropDownList. Because the DropDownList control hasn't been data bound yet it doesn't contain any items. Therefore, you need to call DataBind() first. This gets the genres from the database using EF and puts them in the DropDownList. Once that's done and the item is found in the Items collection, it's made the active item by setting its Selected property to True/true. TheSqlDataSource control watches this DropDownList so when the data source gets its reviews, it does so for the correct genre.
Exercise 3 Solution
The various data-bound controls can raise exceptions that you can handle in their event handlers.
Once you have dealt with the exception appropriately, you need to set the ExceptionHandled property of the e argument to True. The following code snippet shows how a Label control is updated with an error message. ExceptionHandled is then set to stop the Exception from getting passed on into the user interface where it would otherwise result in a “Yellow Screen of Death.”
VB.NET
Protected Sub SqlDataSource1_Deleted(sender As Object,
e As SqlDataSourceStatusEventArgs) Handles SqlDataSource1.Deleted
If e.Exception IsNot Nothing Then
ErrorMessage.Text = "We're sorry, but something went terribly wrong while " &
"deleting your genre."
e.ExceptionHandled = True
End If
End Sub
C#
protected void SqlDataSource1_Deleted(object sender,
SqlDataSourceStatusEventArgs e)
{
if (e.Exception != null)
{
ErrorMessage.Text = @"We're sorry, but something went terribly wrong while
deleting your genre.";
e.ExceptionHandled = true;
}
}
Chapter 16
Exercise 1 Solution
Authentication is all about proving your identity to a system like a website. After you have been authenticated, authorization then determines what you can and cannot do in the system.
Exercise 2 Solution
To expand the access to the Management folder for John and all users in the Editors role, you need to expand the current roles attribute to include Editors, and add an additional allow element with its users attribute set to John:
<system.web>
<authorization>
<allow roles="Managers, Editors"/>
<allow users="John"/>
<deny users="*"/>
</authorization>
</system.web>
The roles attribute enables you to specify multiple roles separated by a comma. To grant the John account access, you need to add an additional allow element and then fill in John's name in the users attribute.
From a maintainability perspective, it would be a lot better to add John to the Managers or Editors role if possible. However, you may end up giving John more rights than you want (he could then access anything that a Manager or an Editor could access). Generally, it's best to manage users through roles as much as possible, but it's good to know that you can grant individual accounts the necessary rights as well (or explicitly take those rights away using a deny element).
Exercise 3 Solution
If you want to redirect all users to the same page, all you need to set is the DestinationPageUrl:
<asp:Login ID="Login1" runat="server" DestinationPageUrl="~/MyProfile.aspx">
When a user is logged in successfully, she's taken to MyProfile.aspx automatically.
Exercise 4 Solution
The LoginStatus simply displays a simple link that indicates whether or not the user is logged in. By default the text that is displayed is Login when the user is currently not logged in, and Logout when the user is already logged in. Clicking the link either sends the user to the default Login page, or logs the user out.
The LoginView is somewhat similar in that it displays different content depending on whether the user is currently logged in. However, because the control is completely template driven, you can fully control the content that is displayed. To enable you to differentiate between different user roles, you can use the RoleGroups element to set up templates that are shown only to users in specific roles.
Chapter 17
Exercise 1 Solution
You would implement the favorite theme as a String property and call it FavoriteTheme. To ensure that you always have a valid theme, you could also set a default value. Finally, you should make the property accessible to anonymous users. Your final profile property could end up like this:
<add name="FavoriteTheme"
defaultValue="Monochrome"
allowAnonymous="true"/>
To support anonymous profiles, you need to explicitly enable them by adding an <anonymousIdentification> element as a direct child of <system.web> in the Web.config file:
<system.web>
<anonymousIdentification enabled="true" cookieName="PlanetWroxAnonymous"/>
Exercise 2 Solution
Given the syntax you saw in the question, you could now access the new property and use it to change the current theme in the BasePage:
VB.NET
Private Sub Page_PreInit(sender As Object, e As EventArgs) _
Handles Me.PreInit
Dim myProfile As ProfileCommon =
CType(HttpContext.Current.Profile, ProfileCommon)
If Not String.IsNullOrEmpty(myProfile.FavoriteTheme) Then
Page.Theme = myProfile.FavoriteTheme
End If
End Sub
C#
private void BasePage_PreInit(object sender, EventArgs e)
{
ProfileCommon myProfile = (ProfileCommon) HttpContext.Current.Profile;
if (!string.IsNullOrEmpty(myProfile.FavoriteTheme))
{
Page.Theme = myProfile.FavoriteTheme;
}
}
Exercise 3 Solution
To finalize the theme selector using Profile, you also need to change the code in the master page Frontend.master. Instead of storing the user-selected theme in a cookie, you should now store it in Profile. Change the code in Page_Load as follows:
VB.NET
If Not Page.IsPostBack Then
Dim selectedTheme As String = Page.Theme
If Not String.IsNullOrEmpty(Profile.FavoriteTheme) Then
selectedTheme = Profile.FavoriteTheme
End If
If Not String.IsNullOrEmpty(selectedTheme) Then
Dim item As ListItem = ThemeList.Items.FindByValue(selectedTheme)
If item IsNot Nothing Then
item.Selected = True
End If
End If
End If
Select Case Page.Theme.ToLower()
C#
if (!Page.IsPostBack)
{
string selectedTheme = Page.Theme;
if (!string.IsNullOrEmpty(Profile.FavoriteTheme))
{
selectedTheme = Profile.FavoriteTheme;
}
if (!string.IsNullOrEmpty(selectedTheme))
{
ListItem item = ThemeList.Items.FindByValue(selectedTheme);
if (item != null)
{
item.Selected = true;
}
}
}
switch (Page.Theme.ToLower())
You can then simplify the code in ThemeList_SelectedIndexChanged in the master page to:
VB.NET
Protected Sub ThemeList_SelectedIndexChanged(sender As Object,
e As EventArgs) Handles ThemeList.SelectedIndexChanged
Profile.FavoriteTheme = ThemeList.SelectedValue
Response.Redirect(Request.Url.ToString())
End Sub
C#
protected void ThemeList_SelectedIndexChanged(object sender, EventArgs e)
{
Profile.FavoriteTheme = ThemeList.SelectedValue;
Response.Redirect(Request.Url.ToString());
}
Chapter 18
Exercise 1 Solution
Debugging is the process of watching your code execute in the development environment—investigating variables and looking into objects in order to understand the execution path of your code—looking for bugs with the aim to fix them. Debugging usually takes place at development time in your Visual Studio IDE.
Tracing, on the other hand, provides you with information on the runtime execution of your code. As discussed in this chapter, you can use tracing to get information about events that fire and the order in which they fire. Additionally, you can add your own information to the trace. Because disabling tracing through configuration greatly minimizes the performance overhead associated with it, you can leave your trace calls in the code, making it easy to disable and enable tracing whenever you need it.
Exercise 2 Solution
The best way to stop a possible exception from ending up in the user interface is to wrap your code in a Try/Catch block. That way you can display an error message to the user in case something goes wrong. Your code could end up looking like this:
VB.NET
Try
' Execute code here to send an e-mail.
Catch ex As SmtpException
ErrorMessage.Text = "Something went wrong while sending your message."
End Try
C#
try
{
// Execute code here to send an e-mail.
}
catch (SmtpException ex)
{
ErrorMessage.Text = "Something went wrong while sending your message.";
}
Exercise 3 Solution
To understand which exceptions occur in the site and find out where they occur (that is, what pages or code files are causing the exceptions) you can log all exceptions using some code in the Application_Error event handler. The exception details you can intercept in this method should help you understand the cause of the exception, which in turn should help in finding a fix for it.
To prevent your users from seeing the “Yellow Screen of Death” error messages, you need to use custom error pages. You should create a simple Web Form that tells the user something went wrong.
To tell the ASP.NET run time to show the contents of that file instead of the error message, you need the following element in your Web.config:
<customErrors mode="On" defaultRedirect="~/Errors/AllOtherErrors.aspx"
redirectMode="ResponseRewrite">
<error statusCode="500" redirect="~/Errors/Error500.aspx"/>
</customErrors>
This element sets up a special page for error code 500 that occurs when your code crashes unexpectedly.
When other exceptions occur, such as a “Page not found” error, users are sent to the more generic AllOtherErrors.aspx page.