Beginning ASP.NET-4.5 in C# and VB (2013)
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 SolutionThe 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 SolutionYou have a number of ways to reset part of the customization changes you may have made, including:
- Resetting the window layout by choosing Window ⇒ Reset Window Layout.
- Resetting the Toolbox by right-clicking it and choosing Reset Toolbox.
- Resetting all settings of Visual Studio using Tools ⇒ Settings ⇒ Import and Export Settings or Tools ⇒ Import and Export Settings, depending on your version of Visual Studio.
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 SolutionA number of files fall in the Web Files category, including .aspx
files (Web Forms that end up as 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 list of files.
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.
The first way is using the Solution Explorer. Right-click your site, choose Add ⇒ 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 your desktop 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 folderC:\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.
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. The new HTML editor (which you cannot use for Web Forms) only has a Markup View.
CHAPTER 3 Exercise 1 SolutionThe 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 SolutionThe 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.
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 selectorBoxWithBorders
, 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.
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 ⇒ 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.
The mechanism that enables controls to maintain their state is called View State.
Exercise 2 SolutionThe 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.
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.
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 its Height
and Width
properties.
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.
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.
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.
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.
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 EnableTheming
to False
.
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.
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.
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 SolutionTo redirect a user to another page programmatically, you can use Response.Redirect, Response.RedirectPermanent
, or 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.
To display a TreeView
that doesn’t have the ability to expand or collapse nodes, you need to set its ShowExpandCollapse
property to False
.
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, during both 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.
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.
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 SolutionIf 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.
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.
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
and args
; 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.
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 the ContactForm.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 is responsible for the partial page updates. You usually place the ScriptManager
directly 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 the ScriptManager
. The ScriptManagerProxy
serves as a bridge between the content page and the ScriptManager
, giving you great flexibility as to where you register your services.
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 user an update is in progress. A typical <ProgressTemplate>
contains an animated icon, some text, or both.
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
(
CHAPTER 11 Exercise 1 Solution
parameters, successCallback, errorCallback
);
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>
$(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.
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.
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 SolutionThe 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.
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.
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
.
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 SqlDataSource
control. Chapter 14 provides you with alternatives for both the GridView
and the SqlDataSource
.
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.
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
.
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).ToList()
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).ToList();
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.
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.
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?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?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.
To delete the picture, you need to extend the code in ListView1_DeleteItem
. You can retrieve the ImageUrl
from the picture that is about to be deleted, convert the URL to a physical location, and delete the image using a call to File.Delete
, like this:
VB.NET
Dim fileName As String = Server.MapPath(picture.ImageUrl)
System.IO.File.Delete(fileName)
myEntities.Pictures.Remove(picture)
C#
string fileName = Server.MapPath(picture.ImageUrl);
System.IO.File.Delete(fileName);
myEntities.Pictures.Remove(picture);
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
.
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 thePage_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
. The SqlDataSource
control watches this DropDownList
so when the data source gets its reviews, it does so for the correct genre.
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 SolutionTo 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).
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.
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.
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>
Exercise 2 Solution
<anonymousIdentification enabled="true" cookieName="PlanetWroxAnonymous" />
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 SolutionThe 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.