 Monday, May 01, 2006
As mentioned earlier, a Web Part can be developed as a user control (which is like a partial page, with markup code and a code-behind) or a custom control (which is a class written in C# for which you create the output 100% programmatically). The choice between the two depends on your needs and requirements, and it’s much like the choice between writing user and custom controls in general. If you want to compile everything into an assembly—so that the source code is protected and the output cannot be modified by an external developer and shared among multiple web sites by installing it in the GAC—then you’ll want to go with custom controls. If, instead, you don’t care much about those aspects, but prefer simplicity and speed of development, and the ease of changing the appearance of the Web Part by working with markup code instead of with C# code, then user controls will be your best bet. In this section, I’ll provide a quick overview of both approaches.
Building a Web Part as a User Control
For our first example we’ll build a simple online calculator, with two textboxes for the operands, a button to submit the form and do the calculation, and a label to show the result. You define the UI with the usual markup code, in the .ascx file (typical of user controls):
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="Calculator.ascx.cs"
Inherits=" Calculator" %>
Op1: <asp:TextBox ID="txtOp1" runat="server" /><br />
Op2: <asp:TextBox ID="txtOp2" runat="server" /><br />
<asp:Button ID="btnCalc" runat="server" Text="Calculate"
OnClick="btnCalc_Click" /><br />
<asp:Label ID="lblResult" runat="server" />
The code-behind needs a property that specifies the type of operation to perform: addition, subtraction, division, or multiplication. The property is named Operation, and is of type OperationType, an enumeration that contains the values indicated above. Then, when the button is clicked, you simply retrieve the input strings, convert them to integers, perform the specified operation, and show the result (I’m not showing the type checks and other validations in order to keep this simple):
public enum OperationType : int
{
Addition,
Subtraction,
Division,
Multiplication
}
public partial class Calculator : System.Web.UI.UserControl
{
private OperationType _operation = OperationType.Addition;
public OperationType Operation
{
get { return _operation; }
set { _operation = value; }
}
protected void btnCalc_Click(object sender, EventArgs e)
{
int op1 = Convert.ToInt32(txtOp1.Text);
int op2 = Convert.ToInt32(txtOp2.Text);
if (this.Operation == OperationType.Addition)
lblResult.Text = (op1 + op2).ToString();
else if (this.Operation == OperationType.Subtraction)
lblResult.Text = (op1 - op2).ToString();
if (this.Operation == OperationType.Division)
lblResult.Text = ((double)op1 / (double)op2).ToString();
else
lblResult.Text = (op1 * op2).ToString();
}
}
So far this is a 100% standard user control. Even so, it can be used as a Web Part on a page. Actually, any control can be used as a Web Part: ASP.NET will take care of wrapping it into a GenericWebPart at runtime. However, to enable users to personalize it by setting the properties, you need to add some attributes to the public properties that you want to make editable at runtime. In particular, the WebBrowsable attribute specifies that the property will be visible in the Web Part’s Editor box; the Personalizable attribute specifies whether the property is editable (either for the shared view or also at the user level in the personal view); the WebDisplayName specifies the property title in the editor box, so that it’s more understandable and user friendly; and the WebDescription attribute is a longer description of the property. In this example, you would decorate the Operation property with attributes as follows:
private OperationType _operation = OperationType.Addition;
[Personalizable(PersonalizationScope.User),
WebBrowsable,
WebDisplayName("Operation Type"),
WebDescription("The type of operation performed when submit is clicked.")]
public OperationType Operation
{
get { return _operation; }
set { _operation = value; }
}
Because this is just a normal user control that ASP.NET will wrap with GenericWebPart, it doesn’t specify Web Part–specific attributes such as the title that should be listed on the Web Parts catalog, or an icon. You can add those attributes to a user control by implementing the IWebPart interface, which defines properties with self-descriptive names such as Title, Description, TitleIconImageUrl, CatalogIconImage, and TitleUrl. You implement these properties as simple wrappers for private fields, as follows:
public partial class Controls_Calculator : System.Web.UI.UserControl, IWebPart
{
private string _catalogIconImageUrl = "";
public string CatalogIconImageUrl
{
get { return _catalogIconImageUrl; }
set { _catalogIconImageUrl = value; }
}
private string _description = "";
public string Description
{
get { return _description; }
set { _description = value; }
}
protected string _subTitle = "";
public string Subtitle
{
get { return _subTitle; }
set { _subTitle = value; }
}
protected string _title = "Online Calculator";
public string Title
{
get { return _title; }
set { _title = value; }
}
private string _titleIconImageUrl = "";
public string TitleIconImageUrl
{
get { return _titleIconImageUrl; }
set { _titleIconImageUrl = value; }
}
private string _titleUrl = "";
public string TitleUrl
{
get { return _titleUrl; }
set { _titleUrl = value; }
}
// ...the rest of the class as shown earlier...
}
Note that the _title field is initialized with “Online Calculator”, which is the string that will be shown in the Web Part catalog to refer to this Web Part, and on the title bar of the Web Part itself when added on the page. You’ll learn how to build and use the catalog shortly.
NOTE: This excerpt was taken from the book "ASP.NET 2.0 Website Programming". Click here to find more about it, and download the complete source code of the sample project.
 Saturday, April 22, 2006
The System.Net.Mail namespace defined in the System.dll assembly contains all the classes used to send e-mails. The older System.Web.Mail namespace, and its related classes, that were used with ASP.NET 1.x are still there, but its use has been deprecated now in favor of these new classes in ASP.NET 2.0 that provide more features. The principal classes are MailMessage, which represents an e-mail message, and the SmtpClient class, which provides the methods used to send a MailMessage by connecting to a configured SMTP server (SMTP is the Simple Mail Transfer Protocol, which is the low-level protocol used by Microsoft Exchange and other mail servers).
MailMessage fully describes an e-mail message, with its subject, body (in plain-text, HTML, or in both formats), the To, CC, and BCC addresses, and any attachments that might be used. The simplest way to create an e-mail is using the MailMessage constructor, which takes the sender’s address, the recipient’s address, the mail’s subject, and the body, as shown below:
MailMessage mail = new MailMessage(
"from@somewhere.com", "to@somewhere.com", "subject", "body");
However, this approach will be too limited in most cases, because you may want to specify the sender’s display name in addition to his e-mail address (the display name is what is displayed by the mail client, if present, instead of the address, and makes the mail and its sender look more professional). You may also want to send to more than one recipient, use an a HTML body (as an alternative, or in addition, to the plain-text version), include some attachments, use a different encoding, modify the mail’s priority, and so on. All these settings, and more, are specified by means of a number of instance properties of the MailMessage class. Their names should be self-explanatory, and some examples include the following: Subject, Body, IsBodyHtml, From, To, CC, Bcc, BodyEncoding, Attachments, AlternateViews, Headers, Priority, and ReplyTo. The class’ constructor enables you to specify a From property of type MailAddress, Address, and UserName properties. The To, CC, and Bcc properties are of type MailAddressCollection, and thus can accept multiple MailAddress instances (you can add them by means of the collection’s Add method). Similarly, the MailMessage’s Attachments property is of type AttachmentCollection, a collection of Attachment instances that point to files located on the server. The following example shows how to build a HTML-formatted e-mail message that will be sent to multiple recipients, with high priority, and that includes a couple of attachments:
// create the message
MailMessage mail = new MailMessage();
// set the sender's address and display name
mail.From = new MailAddress("mbellinaso@wrox.com", "Marco Bellinaso");
// add a first recipient by specifying only her address
mail.To.Add("john@wroxfans.com");
// add a second recipient by specifying her address and display name
mail.To.Add(new MailAddress("anne@wroxfans.com", "Anne Gentle"));
// add a third recipient, but to the CC field this time
mail.CC.Add("mike@wroxfans.com");
// set the mail's subject and HTML body
mail.Subject = "Sample Mail";
mail.Body = "Hello, <b>my friend</b>!<br />How are you?";
mail.IsBodyHtml = true;
// set the mail’s priority to high
mail.Priority = MailPriority.High;
// add a couple of attachments
mail.Attachments.Add(
new Attachment(@"c:\demo.zip", MediaTypeNames.Application.Octet));
mail.Attachments.Add(
new Attachment(@"c:\report.xls", MediaTypeNames.Application.Octet));
If you also wanted to provide a plain-text version of the body in the same mail, so that the display format (plain text or HTML) would depend on the user’s e-mail client settings, you would add the following lines:
string body = "Hello, my friend!\nHow are you?";
AlternateView plainView = new AlternateView(body, MediaTypeNames.Text.Plain);
mail.AlternateViews.Add(plainView);
Once a MailMessage object is ready, the e-mail message it describes can be sent out by means of the Send method of the SmtpClient class, as shown here:
SmtpClient smtpClient = new SmtpClient();
smtpClient.Send(mail);
Before calling the Send method, you may need to set some configuration settings, such as the SMTP server’s address (the SmtpClient’s Host property), port (the Port property) and its credentials (the Credentials property), whether the connection in encrypted with SSL (the EnableSsl property), and the timeout in milliseconds for sending the mail (the Timeout property, which defaults to 100 seconds). An important property is DeliveryMethod, which defines how the mail message is delivered. It’s of type SmtpDeliveryMethod, an enumeration with the following values:
-
Network: The e-mail is sent through a direct connection to the specified SMTP server.
-
PickupDirectoryFromIis: The e-mail message is prepared and the EML file is saved into the default directory from which IIS picks up queued e-mails to send. By default this is <drive>:\Inetpub\mailroot\Queue.
-
SpecifiedPickupDirectory: The EML file with the mail being sent is saved into the location specified by the PickupDirectoryLocation property of the smtpClient object. This is useful when you have an external custom program that picks up e-mails from that folder and processes them.
The delivery method you choose can dramatically change the performance of your site when sending many e-mails, and can produce different errors during the send operation. If you select the Network delivery method, the SmtpClient class takes care of sending the mail directly, and raises an error if the destination e-mail address is not found or if there are other transmission problems. With the other two methods, instead of sending the message directly, an EML mail file is prepared and saved to the file system, where another application (IIS or something else) will pick them up later for the actual delivery (a queue accumulates the messages, which means the web application will not have to wait for each message to be sent over the Internet). However, when using the second and third delivery methods, your web application cannot be notified of any errors that may occur during transmission of the message, and it will be up to IIS (or another mail agent that might be used) to handle them. In general, the PickupDirectoryFromIis method is the preferred one, unless your ASP.NET application is not given the right to write to IIS mail folders (check with your web hosting provider service if you don’t use your own servers).
If you set all SmtpClient properties mentioned above directly in your C# code, you’ll have to recompile the application or edit the source file every time you want to change any of these settings. This, of course, is not an option if you’re selling a packaged application, or you want to let the administrator change these settings on his own without directly involving you. As an alternative to hard-coding the delivery method, you can set it declaratively in the web.config file, which now supports a new configuration section named <mailSettings>, located under <system.net>, which allows you to specify delivery settings. The SmtpClient class automatically loads those settings from web.config to configure itself at runtime, so you should generally not set your delivery and SMTP options directly in your C# code. Following is an extract of the configuration file that shows how to select PickupDirectoryFromIis as the delivery method, set up the sender’s e-mail address and the SMTP server’s name (or IP address) and port, and specify that you want to use the default credentials to connect to the server:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.net>
<mailSettings>
<smtp deliveryMethod="PickupDirectoryFromIis"
from="mbellinaso@wrox.com">
<network defaultCredentials="true"
host="vmwin2003" port="25"></network>
</smtp>
</mailSettings>
</system.net>
<!-- other configuration sections... -->
</configuration>
The SmtpClient’s Send method used in the preceding code snippet sends the e-mail synchronously, which means that the task must complete before the execution of your application can resume. The term synchronous means “do what I asked, and I’ll stop and wait for you to finish,” and the term asynchronous means “do what I asked, but let me continue doing other work, and you should notify me when you’re done.” The SmtpClient class also has a SendAsync method to send the mail asynchronously. It returns immediately, and the e-mail is prepared and sent out on a separate thread. When the send task is complete, the SmtpClient’s SendCompleted event is raised. This event is also raised in case of errors, and the Error and Cancelled properties of its second argument (of type AsyncCompletedEventArgs) tells you whether it was raised because the send was cancelled, because there was an error, or because the send completed successfully. Here’s a sample snippet that shows how to send the mail asynchronously, and handle the resulting completion event:
SmtpClient smtpClient = new SmtpClient();
smtpClient.SendCompleted += new SendCompletedEventHandler(MailSendCompleted);
smtpClient.SendAsync(message, null);
...
public static void MailSendCompleted(object sender, AsyncCompletedEventArgs e)
{
if (e.Cancelled)
Trace.Write("Send canceled.");
if (e.Error != null)
Trace.Write(e.Error.ToString());
else
Trace.Write("Message sent.");
}
An asynchronous send operation can be cancelled before completion by calling the SmtpClient’s SendAsyncCancel method. Note that you can’t send a second e-mail while a SmtpClient has another send in progress; if you try to do so, you’ll receive an InvalidOperationException.
NOTE: This excerpt was taken from the book "ASP.NET 2.0 Website Programming". Click here to find more about it, and download the complete source code of the sample project.
The more meaningful and short a URL can be, the better. Those URLs are easier to communicate to people, and easier for them to remember. The current URLs that allow us to browse articles for a specific category are already pretty short, but it’s not easy to remember all category IDs by heart . . . and thus to remember the URL. ASP.NET 2.0 introduces a new section in web.config that allows us to map a virtual URL to a real URL: This section’s name is urlMapping, and it’s located under <system.web>. When the user types the virtual URL, the page located at the corresponding real URL will be loaded. Here’s what you can write in web.config to make your sample categories easier to reach:
<urlMappings>
<add url="~/articles/beer.aspx" mappedUrl="~/BrowseArticles.aspx?CatID=28" />
<add url="~/articles/events.aspx" mappedUrl="~/BrowseArticles.aspx?CatID=41" />
<add url="~/articles/news.aspx" mappedUrl="~/BrowseArticles.aspx?CatID=31" />
<add url="~/articles/photos.aspx" mappedUrl="~/BrowseArticles.aspx?CatID=40" />
<add url="~/articles/blog.aspx" mappedUrl="~/BrowseArticles.aspx?CatID=29" />
<add url="~/articles/faq.aspx" mappedUrl="~/BrowseArticles.aspx?CatID=42" />
</urlMappings>
Note that when the server receives a request for the virtual URL it doesn’t make a normal redirect to the real URL. Before the page is loaded, ASP.NET rewrites the URL to the request’s context. From that point on, the URL will be read from the context, and thus the correct URL will be retrieved. The bottom line is that the transition is done in memory on the server side, and users do not even see the real URL in their browser. Therefore, if a user maneuvers to ~/articles/beer.aspx she’s really going to get ~/BrowseArticles.aspx?CatID=28. Furthermore, there is no physical page named ~/articles/beer.aspx.
This could have been done in ASP.NET 1.1, but you had to write your own custom HTTP module to do that, from which you would have used the current HttpContext’s RewritePath method to do the in-memory redirect.
NOTE: This excerpt was taken from the book "ASP.NET 2.0 Website Programming". Click here to find more about it, and download the complete source code of the sample project.
 Thursday, April 13, 2006
In the ASP.NET 1.x days, if you wanted to associate a profile to a registered user, you typically added a custom table to your database, or stored them together with the user credentials, in the same table. You also had to write quite a lot of code for the business and data access layers, to store, retrieve, and update that data from your web pages. ASP.NET 2.0 provides a built-in mechanism to manage user profiles, in an easy, yet very complete and flexible, way. This new feature can save you hours or even days of work! The Profile module takes care of everything—you just need to configure what the profile will contain, i.e., define the property name, type, and default value. This configuration is done in the root web.config file, within the <profile> section. The following snippet shows how to declare two properties, FavoriteTheme of type String, and BirthDate of type DateTime:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<profile>
<properties>
<add name="FavoriteTheme" type="String" />
<add name="BirthDate" type="DateTime" />
</properties>
</profile>
<!-- other settings... -->
</system.web>
</configuration>
Amazingly, this is all you need to do to set up a profile structure! When the application is run, the ASP.NET runtime dynamically adds a Profile property to the Page class, which means you will not find such a property in the Object Browser at design time. The object returned is of type ProfileCommon (inherited from System.Web.Profile.ProfileBase); you will not find this class in the Object Browser either, or on the documentation, because this class is generated and compiled on-the-fly, according to the properties defined in the web.config file. The result is that you can just access the page’s Profile property and read/write its subproperties. The following code demonstrates how to read the values of the current user’s profile to show them on the page when it loads, and then updates them when a Submit button is clicked:
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
ddlThemes.SelectedValue = this.Profile.FavoriteTheme;
txtBirthDate.Text = this.Profile.BirthDate.ToShortDateString();
}
}
protected void btnSubmit_Click(object sender, EventArgs e)
{
this.Profile.FavoriteTheme = ddlThemes.SelectedValue;
this.Profile.BirthDate = DateTime.Parse(txtBirthDate.Text);
}
Even though you can’t see these properties in the Object Browser, Visual Studio .NET is smart enough to compile this class in the background when the web.config file is modified, so you get full IntelliSense in the IDE, just as if the Profile properties were built-in properties of the Page class, like all the others. Figure 4-11 is a screenshot of the IDE with the IntelliSense in action.
Figure 4-11
Having a class dynamically generated by Visual Studio 2005 with all the custom profile properties (and the IntelliSense for them) doesn’t just speed up development, but also helps developers reduce inadvertent coding errors. In fact, this class provides strongly typed access to the user’s profile, so if you try to assign a string or an integer to a property that expects a date, you’ll get a compile-time error so you can correct the problem immediately!
When you define a profile property, you can also assign a default value to it, by means of the defaultValue attribute:
<add name="FavoriteTheme" type="String" defaultValue="Colorful" />
The default value for strings is an empty string, not null, as you may have thought. This makes it easier to read string properties, because you don’t have to check whether they are null before using the value somewhere. The other data types have the same default values that a variable of the same type would have (e.g., zero for integers).
When you declare profile properties, you can also group them into subsections, as shown below:
<profile>
<properties>
<add name="FavoriteTheme" type="String" />
<add name="BirthDate" type="DateTime" />
<group name="Address">
<add name="Street" type="String" />
<add name="City" type="String" />
</group>
</properties>
</profile>
The Street property will be accessible as Profile.Address.Street. Note, however, that you can’t define nested groups under each other, but can only have a single level of groups. If this limitation is not acceptable to you, you can define your own custom class with subcollections and properties, and reference it in the type attribute of a new property. In fact, you are not limited to base types for profile properties; you can also reference more complex classes (such as ArrayList or Color), and your own enumerations, structures, and classes, as long as they are serializable into a binary or XML format (the format is dictated by the property’s serializeAs attribute).
The Profile system is built upon the provider model design pattern. ASP.NET 2.0 comes with a single built-in profile provider that uses a SQL Server database as a backing store. However, as usual, you can built your own providers or find them from third parties.
NOTE: This excerpt was taken from the book "ASP.NET 2.0 Website Programming". Click here to find more about it, and download the complete source code of the sample project.
 Friday, April 07, 2006
A very important issue you must tackle when designing a business layer is how you plan to manage transactions. Many business methods call multiple DAL methods internally to update, insert, or delete multiple records, potentially in multiple tables. You must ensure that multiple calls run within a transaction, so that if one fails, all actions performed by previous methods are rolled back. If you don’t do this, you’ll end up having inconsistent, and wrong, data. Managing transactions would be complicated if you had to do everything yourself, but fortunately there are several technologies and frameworks that can do the plumbing for you.
In the simplest case you can use explicit ADO.NET client-side transactions. You should already be familiar with them, but here’s some sample code that runs a couple of commands inside a transaction to refresh your memory:
using (SqlConnection cn = new SqlConnection(connString))
{
cn.Open();
SqlTransaction tran = cn.BeginTransaction();
try
{
SqlCommand cmd1 = new SqlCommand(cmdText1, cn, tran);
cmd1.ExecuteNonQuery();
SqlCommand cmd2 = new SqlCommand(cmdText2, cn, tran);
cmd2.ExecuteNonQuery();
tran.Commit();
}
catch(Exception e)
{
tran.Rollback();
}
}
The preceding code is simple and works fine in many situations. However, the transactions managed by ADO.NET are connection-bound, which implies the following limitations:
-
You have to use them from the DAL, where you have the connection objects, and not from the BLL where you’ll typically (but not necessarily) want to manage transactions. This is not a problem if you’re employing a lighter architecture with the DAL and BLL mixed together in a single layer, which may actually be a valid choice for small sites, but it does pose a problem for multi-tier systems.
-
The transaction is bound to a single connection, which means it can’t span multiple databases. This may be required if, for example, you store all data for the forums module in one database and the articles data on some other database, and you need some business method that updates some records in both systems within one logical transaction.
-
All commands you execute must use the same connection. If these commands are executed from different methods, wrapping them into a single transaction means that you must find some way to pass the connection object through all the methods. This could be done with an additional method parameter, but this leads to an ugly and inflexible design.
All of the problems presented can be solved by using COM+ as the application server for your components. COM+ can handle transactions that include multiple commands and multiple connections (also to different data stores), and generally any action that the DTC (Distributed Transaction Coordinator) can manage, such as sending a MSMQ message. By using COM+ you can easily write atomic and isolated procedures; once you start the transaction in a method, you can have all submethods enlist into the transaction automatically, and have the transaction be committed if no exception is thrown or otherwise rolled back. If your class inherits from System.EnterpriseServices.ServicedComponent, the transaction handling is configurable by means of .NET attributes (e.g., AutoCompleteAttribute, for the automatic transaction completion just outlined) that you apply at the assembly, class, and method level. Here’s a sample transactional class that uses attributes to configure automatic transactions:
[Transaction(TransactionOption.RequiresNew)]
public class SampleBO : ServicedComponent
{
[AutoComplete]
public void UpdateDate()
{
MyBizObject1 obj1 = new MyBizObject1();
obj1.DoSomething();
MyBizObject2 obj2 = new MyBizObject2();
obj2.DoSomethingElse();
}
}
If you don’t like declarative transactions, you can still handle them imperatively in code through the ContextUtil helper class, and its EnableCommit, DisableCommit, SetAbout, and SetComplete methods.
COM+ transactions add a lot of overhead to simple ADO.NET transactions, and as a result they are much slower (even 50%). However, COM+ doesn’t just mean transactions, but also object polling, just-in-time activation, queued components and much more—all features that can make your application more reliable and scalable, which is often more important than plain performance statistics. And, after all, if your application shuts down after a high load, how useful is it to know that it was extremely fast with a few users, when it was started? The problem with using COM+ in .NET is that your business objects must inherit from System.EnterpriseServices.ServicedComponents and must respect a number of rules (for example, you can’t define static methods), and you must make this decision early on; otherwise, adding transaction support later will require a lot of additional work. Another problem is that deploying .NET Enterprise Services is not as easy as deploying a normal assembly, as you must generate a COM type library from the assembly, and register it into the Windows registry and the COM+ catalog. These are operations that only a system administrator for the remote server can do, so you won’t be able to do this if you’re planning to deploy your site using an inexpensive shared hosting provider service.
Fortunately, though, if you’re hosting the site on Windows Server 2003, you can take advantage of a new feature of COM+ 1.5 called Services Without Components (SWC). This should also work on Windows XP, but you should never deploy a production web application to a client version of Windows for performance and reliability reasons. This feature allows you to configure, start, and manage a distributed transaction without actually writing a typical COM+ component that must be registered in the COM+ catalog. With the .NET Framework 1.1, you can do everything with the ServiceConfig, ServiceDomain, and ContextUtil classes that you find in the System.EnterpriseServices.dll assembly, under the System.EnterpriseServices namespace. You can configure the transaction on-the-fly by creating an instance of ServiceConfig and setting the transaction type (Transaction and IsolationLevel properties), specifying whether tracking is enabled (TrackingEnabled property), the application and component name (TrackingAppName and TrackingComponentName properties), and other options. Finally, you start the transaction by calling the Enter static method of the ServiceDomain class, which takes the ServiceConfig object that specifies the configuration. You use the SetComplete and SetAbort static methods of the ContextUtil class to commit or roll back the transaction, respectively. Here’s an example:
// configure and start the transaction
ServiceConfig svcConfig = new ServiceConfig();
svcConfig.TrackingEnabled = true;
svcConfig.TrackingAppName = "TheBeerHouse";
svcConfig.TrackingComponentName = "MB.TheBeerHouse.BLL.SampleBO";
svcConfig.Transaction = TransactionOption.RequiresNew;
svcConfig.IsolationLevel = TransactionIsolationLevel.ReadCommitted;
ServiceDomain.Enter(svcConfig);
try
{
MyBizObject1 obj1 = new MyBizObject1();
obj1.DoSomething();
MyBizObject2 obj2 = new MyBizObject2();
obj2.DoSomethingElse();
ContextUtil.SetComplete();
}
catch (Exception e)
{
// rollback the transaction
ContextUtil.SetAbort();
}
finally
{
ServiceDomain.Leave();
}
This code wraps the calls to two different business objects into a single distributed transaction controlled by the DTC. You only have to start the transaction, catch exceptions that may be thrown, and commit or roll back the transaction. You don’t have any special deployment needs: a simple XCopy is enough. Also, SWC is good because you can easily add transactions to business objects that weren’t originally designed to handle distributed transactions—namely, objects that don’t inherit from ServicedComponent, and that call DAL methods that are not ADO.NET transaction-aware (that don’t pass Transaction objects as parameters). SWC, however, doesn’t completely replace traditional Enterprise Services components, as they don’t allow you to use other features such as object pooling, just-in-time activation, queued components, and all other COM+ functionality; you can use them just to add transaction support with the least development and deployment effort. All in all, this is a very welcome facility that should be used thoughtfully.
SWC transactions are definitely good, but the version 2.0 of the .NET Framework introduces something even better: a new System.Transactions namespace which provides a modern, managed interface to handle transactions that can not be handled by ADO.NET’s SqlTransaction class! Two new transaction managers were introduced: Lightweight Transaction Manager and OleTx Transaction Manager. The former manages transaction bound to a single durable resource (i.e. a single connection to a single data store), while the latter can manage distributed transaction, where multiple connections to different data stores come into play. You don’t have to choose between the two yourself; a proper transaction manager will be automatically chosen according to the type and the number of resources that you wish to use into a transaction scope.
The basic class that you’ll be using is System.Transactions.TransactionScope. When an object of this type is created, it starts a lightweight transaction. Then you start creating your connections and other transaction-aware resources (such as MSMQ queues and messages). As long as you use a single resource that supports lightweight transactions, the transaction will be handled by the resource manager itself. SQL Server 2005 (including the Express Edition) has this capability, so if you create a single connection to one of its databases, it will take care of everything internally, consuming as little resources as possible, and with very good performance. As soon as you introduce a second connection/resource into the transaction scope however, the transaction manager will automatically be promoted to a OleTx Transaction Manager, which is able to handle distributed transactions by means of the COM+ DTC technology under the covers (it dynamically configures a temporary Enterprise Service through SWC). This also happens if you have a single connection to a resource manager which doesn’t support lightweight transactions, such as SQL Server 7/2000, Oracle, and other RDBMSs. Here’s an example that starts a transaction and runs a couple of different commands within it:
using(TransactionScope scope = new TransactionScope())
{
using (SqlConnection cn = new SqlConnection(connString))
{
cn.Open();
SqlCommand cmd1 = new SqlCommand(cmdText1, cn);
cmd1.ExecuteNonQuery();
SqlCommand cmd2 = new SqlCommand(cmdText2, cn);
cmd2.ExecuteNonQuery();
}
scope.Complete();
}
Since the two commands share the same connection, a lightweight transaction will be created if connString points to a SQL Server 2005 database. As I mentioned before though, transactions are often run from the BLL, and must wrap calls to several other methods, which internally may create separate connections and target different databases. The code is still as simple as the code above, though:
using(TransactionScope scope = new TransactionScope())
{
MyBizObject1 obj1 = new MyBizObject1();
obj1.DoSomething();
MyBizObject2 obj2 = new MyBizObject2();
obj2.DoSomethingElse();
scope.Complete();
}
When this code is run, a distributed COM+ transaction will probably be created under the covers (it depends on whether the two methods use two connections, or share a single one), but the developer doesn’t need to know this, and doesn’t have to do anything special at designtime or deployment time. Another advantage of using System.Transactions is that you can create transactions only in methods where you really need them, and you don’t have to make a whole class transactional. Given how simple it is to work with TransactionScope and related classes, you don’t even need to build a framework or some sort to base service to simplify things, it’s all already there! However, I would not recommend COM+, SWC or System.Transactions for use with shared web hosting because the servers are out of your control and it’s not clear whether this could be used reliably in an environment where server reconfiguration is commonplace. Also, in the sample website for this book we won’t make use of these advanced technologies.
If you want to know more about System.Transactions, refer to Juval Lowy’s whitepaper entitled “Introducing System.Transactions in the Microsoft .NET Framework version 2.0”, downloadable from http://www.microsoft.com/downloads/details.aspx?FamilyId=AAC3D722-444C-4E27-8B2E-C6157ED16B15&displaylang=en
NOTE: This excerpt was taken from the book "ASP.NET 2.0 Website Programming". Click here to find more about it, and download the complete source code of the sample project.
 Wednesday, March 29, 2006
Wow, I just got from Wrox the permission to publish on this site the entire source code for the TheBeerHouse website, even if it's not on their site yet! You can download it from the book's page. Go take it! Hope you have fun with it 
In every site or web-based application there is some data that doesn’t change very often, which is requested very frequently by a lot of end users. Examples are the list of article categories, or the e-store’s product categories and product items, the list of countries and states, and so on. The most common solution to increase the performance of your site is to implement a caching system for that type of data, so that once the data is retrieved from the data store once, it will be kept in memory for some interval, and subsequent requests for the same data will retrieve it from the memory cache, avoiding a round-trip to the database server and running another query. This will save processing time and network traffic, and thus produce a faster output to the user. In ASP.NET 1.x, the System.Web.Caching.Cache class was commonly used to cache data. The cache works as an extended dictionary collection, whereby each entry has a key and a related value. You can store an item in cache by writing Cache.Insert("key", data), and you retrieve it by writing data = Cache["key"]. The Insert method of the Cache class has a number of other overloads through which you can specify either the cached data’s expiration time or how long the data will be kept in the cache, and whether it is a sliding interval (a sliding interval is reset every time the cached data is accessed), plus a dependency to a file or other cached items. When the dependent file is changed, the expiration time is reached, or the interval passes, the data will be purged from the cache, and at the next request you will need to query the data directly from the database, storing it into the cache again.
One limitation of the ASP.NET 1.x cache is that when the expiration time or caching interval is reached, the data is removed from the cache and you have to read it again from the DB even if it hasn’t actually changed in the database. Conversely, if you cache the data for 30 minutes, and the data changes the second after you cache it, you’ll be displaying stale and out-of-sync data for almost 30 minutes. This could be unacceptable for some types of information, such as the price of a product or the number of items in stock. The Cache class has been enhanced for ASP.NET 2.0; it now supports dependencies to database tables, in addition to files and other cached items. In practice, you can cache the data for an indeterminate period, until the data in the source database’s table actually changes. This cache invalidation mechanism works for all versions of SQL Server (version 7 and later), where it is based on polling and triggers. SQL Server 2005 adds another type of cache invalidation based on receiving events from the database, so it’s more efficient if you know you’ll be deploying to SQL Server 2005. In addition, the polling method only watches for table-level changes, but the SQL Server 2005 event method enables you to watch individual rows to see if they’ve been changed.
When using the polling style of cache invalidation, ASP.NET 2.0 checks a counter in a support table every so often (the interval being configurable), and if the retrieved value is greater than the value read on the last check, then the data was changed, and thus it removes it from cache. There is one counter (and therefore one record in the AspNet_CacheTablesForChangeNotification support table) for each table for which you want to add SQL-dependency support. The counter is incremented by a table-specific trigger. You create the required table, trigger, and stored procedure needed to support the SQL dependency system by executing the aspnet_regsql.exe command-line tool from Visual Studio’s command prompt. Run it once to add the support at the database level to create the AspNet_CacheTablesForChangeNotification table and the supporting stored procedure, as follows (this assumes your database is a local SQL Server Express instance named SqlExpress):
aspnet_regsql.exe -E -S .\SqlExpress -d aspnetdb -ed
The -E option specifies that you’re using Windows integrated security and thus don’t need to pass username and password credentials (you would need to use the -U and -P parameters, respectively, otherwise). The -S option specifies the SQL Server instance name (specifying localhost\SqlExpress is the same). SqlExpress is the default instance name you get when you install SQL Server 2005 Express. The -d option specifies the database name (aspnetdb), and the -ed tells it to “enable database.”
The next step is to add support for a specific table, which means you must create a record in the AspNet_CacheTablesForChangeNotification table, and a trigger for the table to which you’re adding support:
aspnet_regsql.exe -E -S .\SqlExpress -d aspnetdb -t Customers -et
In addition to the first command description given earlier, the -t parameter specifies the table name, and the -et parameter stands for “enable table.” For the preceding commands to work, the aspnetdb database must be already attached to a SQL Server instance. This is already the case for SQL Server 7/2000 and for the fully featured editions of SQL Server 2005; however, with SQL Server 2005 Express, you typically have the database attach dynamically at runtime, so that you can do the XCopy deployment for the database as well as for the rest of the site’s files. If that’s your situation, you must first temporarily attach the database file, run aspnet_regsql.exe, and then detach the database. The attachment/detachment can be done by running the sp_attach_db and sp_detach_db system stored procedures. You can execute them from SQL Server Management Studio Express (downloadable from Microsoft if it didn’t come with your SQL Express installation), or from the sqlcmd.exe command-line program, run from the VS 2005’s command prompt. Many of the SQL commands used as examples in this book use the sqlcmd program because everyone should have this program. It is started from a Visual Studio command prompt as follows (the command-line options are similar to those of aspnet_regsql as explained above):
sqlcmd -E -S .\SqlExpress
Once you are in the sqlcmd program, you run the following command to attach the database:
sp_attach_db "aspnetdb", "c:\Websites\TheBeerHouse\App_Data\AspNetDB.mdf"
go
Then run the two aspnet_regsql commands listed above followed by “go” on a separate line to end the batch, and finally detach the database as follows:
sp_detach_db "aspnetdb"
go
To close the sqlcmd shell, just type quit or exit and press Enter. Note that if you were running the stored procedures from SQL Server Management Studio, you would need to replace the double quotes with single quotes, and the GO command would not be needed.
The last thing to do to complete the SQL dependency configuration is to write the polling settings in the web.config file. You can configure different polling profiles for the same database, or different settings for different databases. This is done by adding entries under the system.web/caching/sqlCacheDependency/databases section, as shown below:
<configuration>
<connectionStrings>
<add name="SiteDB" connectionString="Data Source=.\SQLExpress;Integrated Security=True;User Instance=True;AttachDBFilename=|DataDirectory|AspNetDB.mdf" />
</connectionStrings>
<system.web>
<caching>
<sqlCacheDependency enabled="true" pollTime="10000">
<databases>
<add name="SiteDB-Cache" connectionStringName="SiteDB"
pollTime="2000" />
</databases>
</sqlCacheDependency>
</caching>
<!-- other settings here... -->
</system.web>
</configuration>
As you see, there is an entry named SiteDB-cache that refers to the databases pointed to by the connection string called SiteDB (more about this later) and that defines a polling interval of 2 seconds (2,000 milliseconds). If the pollTime attribute is not specified, the default value of 10 seconds (in the sample above) would be used.
Now that everything is configured, you can finally write the code to actually cache the data. To create a dependency to a Customers table, you create an instance of the System.Web.Caching.SqlCacheDependency class, whose constructor takes the caching profile name defined above, and the table name. Then, when you insert the data into the Cache class, you pass the SqlCacheDependency object as a third parameter to the Cache.Insert method, as shown below:
SqlCacheDependency dep = new SqlCacheDependency("SiteDB-cache", "Customers");
Cache.Insert("Customers", customers, dep);
Let’s assume that you have a GetCustomers method in your DAL that returns a list of CustomerDetails objects filled with data from the Customers table. You could implement caching as follows:
public List<CustomerDetails> GetCustomers()
{
List<CustomerDetails> customers = null;
if (Cache["Customers"] != null)
{
customers = (List<CustomerDetails>)Cache["Customers"];
}
else
{
using (SqlConnection cn = new SqlConnection(_connString))
{
SqlCommand cmd = new SqlCommand("SELECT * FROM Customers", cn);
customers = FillCustomerListFromReader(cmd.ExecuteReader());
SqlCacheDependency dep = new SqlCacheDependency(
"SiteDB-cache", "Customers");
Cache.Insert("Customers", customers, dep);
}
}
return customers;
}
The method first checks whether the data is already in cache: If it is, then it retrieves the data from there; otherwise, it first retrieves it from the database, and then caches it for later use.
Not only can you use this caching expiration mechanism for storing data to be accessed from code, you can also use it for the ASP.NET’s Output Caching feature, i.e., caching the HTML produced by page rendering, so that pages don’t have to be re-rendered every time, even when the page’s output would not change. To add output caching to a page, add the @OutputCache page directive at the top of the .aspx file (or the .ascx file if you want to use fragment caching in user controls):
<%@ OutputCache Duration="3600" VaryByParam="None"
SqlDependency="SiteDB-cache:Customers" %>
With this directive, the page’s output will be cached for a maximum of one hour, or less if the data in the Customers table is modified.
The problem with this implementation of the SQL dependency caching is that the dependency is to the entire table; it invalidates the cache regardless of which data in the table is changed. If you retrieved and cached just a few records from a table of thousands of records, why should you purge them when some other records are modified? With SQL Server 7 and 2000 whole-table monitoring for cache dependencies is your only choice, but SQL Server 2005 adds row-specific cache dependency tracking.
The counter- and polling-based SQL dependency implementation just described is fully supported by SQL Server 2005, but this latest version of SQL Server also has some new features and technology built into it that further extend the capabilities of the Cache class. The new engine is able to create an indexed view (a view with a physical copy of the rows) when a query for which the client wants to create a dependency is executed. If after an insert, delete or update statement the results returned by a query would change, SQL Server 2005 can detect this situation and send a message to the client that registered for the dependency, by means of the Service Broker. These Query Notification events sent from SQL Server back to an application program enable a client to be notified when some data it previously retrieved was changed in the DB since it was originally retrieved, so that the client can re-request that data for the latest changes. A new class, System.Data.SqlClient.SqlDependency, can create a dependency tied to a specific SqlCommand, and thus create a logical subscription for change notifications that are received by its OnChange event handler. The following snippet shows how to create such a dependency:
using (SqlConnection cn = new SqlConnection(_connString))
{
cn.Open();
SqlCommand cmd = new SqlCommand(
"SELECT ID, CustomerName FROM dbo.Customers", cn);
SqlDependency dep = new SqlDependency(cmd);
dep.OnChange += new OnChangeEventHandler(CustomersData_OnChange);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
Trace.Write(reader["CustomerName"].ToString());
}
}
Below is the specified event handler for OnChange, raised when the underlying data returned by the preceding query changes in the database:
void CustomersData_OnChange(object sender, SqlNotificationEventArgs e)
{
Trace.Warn("Customers data has changed. Reload it from the DB.");
}
Note that in order for this code to work, you must first enable the Query Notifications support in your client by calling the SqlDependency.Start method once, somewhere in the application. If you’re using it from within a web-based application, the right place to put this call would be in the Application_Start global event handler in global.asax. For a WinForms application, it may be the Main entry-point method, or the main form’s Form_Load event.
The preceding code just shows that we’re being notified when the underlying data in the database has changed, but we normally want to go one step further and purge data from the cache when changes are detected in the database. The ASP.NET’s SqlCacheDependency has other overloaded versions of its constructor, and one of them takes a SqlCommand instance. It creates a SqlDependency object internally behind the scenes, and handles its OnChange event to automatically remove the data from the cache when data for the specific SELECT query would change. Here’s all you have to do to cache some data with a dependency to a SqlCommand:
SqlCommand cmd = new SqlCommand("SELECT ID, CustomerName FROM dbo.Customers", cn);
SqlCacheDependency dep = new SqlCacheDependency(cmd);
Cache.Insert("keyname", data, dep);
The sample GetCustomers method shown above would then become the following:
public List<CustomerDetails> GetCustomers()
{
List<CustomerDetails> customers = null;
if (Cache["Customers"] != null)
{
customers = (List<CustomerDetails>)Cache["Customers"];
}
else
{
using (SqlConnection cn = new SqlConnection(_connString))
{
SqlCommand cmd = new SqlCommand(
"SELECT ID, CustomerName FROM dbo.Customers", cn);
SqlCacheDependency dep = new SqlCacheDependency(cmd);
customers = FillCustomerListFromReader(cmd.ExecuteReader());
Cache.Insert("Customers", customers, dep);
}
}
return customers;
}
This technology has the obvious advantage that the dependency is at the query level, and not at the entire table level like the implementation for SQL Server 7/2000, and the event method is much more efficient than using a polling mechanism. However, it has a number of serious limitations that drastically reduce the number of occasions when it can be used, so sometimes the whole-table polling method is your only choice. Here are the most important constraints:
-
You can’t use the * wildcard in the SELECT query; instead, you must explicitly list all the fields. This is a good practice anyway, because you should only request fields that you actually need and not necessarily all of them. Listing them explicitly also puts you in control of their order in the returned DataReader or DataTable, something that can be important if you access fields by index and not by name (although access by index is not itself a good practice).
-
You must reference any table with the full name, e.g., dbo.Customers. Just Customers wouldn’t be enough. This is a significant issue because most of us aren’t used to fully qualifying table names, but it’s a simple matter to handle if you remember that you need to do it.
-
The query can’t use aggregation functions such as COUNT, SUM, AVG, MIN, MAX, etc.
-
The query can’t use ranking and windowing functions, such as the new ROW_NUMBER() function, which is tremendously useful for implementing high-performance results pagination to be used, for example, in the DataGrid, GridView, or other ASP.NET server-side controls. (This function will be explained in Chapter 5.)
-
You can’t reference views or temporary tables in the query.
-
The query can’t return fields of type text, ntext, or image (blob types). Consider that many tables will have such columns, for containing the description of a product, the content of an article or a newsletter, etc.
-
You can’t use DISTINCT, HAVING, CONTAINS and FREETEXT.
-
The query can’t include subqueries, outer-joins or self-joins. This is one of the biggest limitations, as subqueries are commonly used.
-
All of the preceding limitations exist regardless of whether the query is run directly from the client as a SQL text command, or from inside a stored procedure. For stored procedures, however, there’s a further limitation: You can’t use the SET NOCOUNT ON statement, which is often used (and suggested) to reduce the quantity of information sent across the network, for cases where you don’t need counts.
If you consider that most of the modules developed in the following chapters will need to implement custom pagination to be fast (and thus windowing functions or temporary tables, COUNT, subqueries and other prohibited features), and that many columns will be of type ntext, you can easily understand why you may not be able to use to this form of SQL dependency often.
If you want to know more about the Service Broker and Query Notifications, the technology behind Sql Dependencies, I recommend a whitepaper written by Bob Beauchemin, titled "Query Notifications in ADO.NET 2.0" and available on MSDN Online at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvs05/html/querynotification.asp.
The Cache class has been greatly improved in its latest implementation. However, more is less sometimes, and using your own code to handle the expiration of data and manual purging may be better than using some form of automated SQL dependency. The polling-based approach is done at the table level, so it will often invalidate your data when unrelated data in the same table has been changed. The SQL Server 2005’s Service Broker/Query Notification technology is very intriguing, and will be very handy in some situations, but as I said earlier it suffers from too many limitations to be used often. Additionally, both approaches are bound to SQL Server, and should only be used in the DAL provider specific to SQL Server. Therefore, if we used the SQL dependencies, different providers (for different RDBMSs) should implement a different caching strategy, rewritten from scratch. This is something I don’t like, because I want my caching code in the BLL (not in the DAL), so that it’s executed the same way regardless of which DAL provider is being used. For all these reasons I won’t be using any form of built-in SQL dependency for the modules developed in the rest of the book. Instead, I use the good old ASP.NET 1.x caching features based on time/interval expiration. To avoid displaying stale data, we’ll implement some simple methods that purge data from cache when it actually changes in the database. To call the methods you won’t need to implement triggers at the database level, or use some other connection and notification service between the data store and the application’s code. You’ll just call them from the BLL methods that add, edit and delete data. Because the site contents will be managed by a web-based custom administration console and your own BLL classes, there won’t be a need to intercept the changes at the database level. Instead, you just add some code to the BLL methods themselves. This gives you complete control of what data you need to purge, and when you must actually purge it (e.g., when you remove data if a specific row field changes, but not if another field changes).
NOTE: This excerpt was taken from the book "ASP.NET 2.0 Website Programming". Click here to find more about it, and download the complete source code of the sample project.
I just updated the My Books page, which was just empty until now. You can read a detailed description of the book, chapter by chapter, as well as reading Francesco Balena's foreword. I'll push the Wrox guys so that they upload the complete book's source code as soon as possible, or at least allow me to post it here. I'm really eager to have you donwload it, play with it, and provide feedback . Ah...ehm...the book won't the available in the book stores before the end of April, but you can already preorder it on Amazon. What's really good is that this new edition is longer than the previous one (around 600 pages), but it costs 33% less than it! Even better, thanks to Amazon's 37%-off offer, you can get it for just $25, which I think is a good deal 
After a long time of silence, starting with this entry I'm going to post on this blog a number of excerpts from my new book, "ASP.NET 2.0 Website Programming", soon to be found in all good book stores . Please note that these pieces are taken from the Design section of the chapters, that is where I introduce the new ASP.NET 2.0 features & controls, and use that background information to design the module covered by the chapter. It's in the Solution section that the real code is actually being written to implement the sample website, and it's that part that I believe is more interesting and useful for many developers. However, that section is not very good for taking self-containing excerpts, as it should be read as a whole, and therefore I had to chose some examples from the Design. Hope you appreciate them As usual, any feedback is very welcome!
Enter the Master Page Model (Chapter 2)
ASP.NET 2.0 introduces a new “master page” feature that enables you to define common areas that every page will share, such as headers, footers, menus, and so on. A master page enables you to put the common layout code in a single file and have it visually inherited in all the content pages. A master page contains the overall layout for your site. Content pages can inherit the appearance of a master page, and place their own content where the master page has defined a ContentPlaceHolder control. Although this has the effect of providing a form of visual inheritance, it’s not really implemented with inheritance in an OOP sense—instead, the underlying implementation of master pages is based on a template model.
An example is worth a thousand words, so let’s see how this concept turns into practice. A master page has a .master extension and is similar to a user control under the covers. Following is some code for a master page that contains some text, a header, a footer, and defines a ContentPlaceHolder control between the two:
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="MasterPage.master.cs" Inherits="MasterPage" %>
<html>
<head id="Head1" runat="server">
<title>TheBeerHouse</title>
</head>
<body>
<form id="Main" runat="server">
<div id="header">The Beer House</div>
<asp:ContentPlaceHolder ID="MainContent" runat="server" />
<div id="footer">Copyright 2005 Marco Bellinaso</div>
</form>
</body>
</html>
As you see, it is extremely similar to a standard page, except that it has a @Master directive at the top of the page instead of a @Page directive, and it declares one or more ContentPlaceHolder controls where the .aspx pages will add their own content. The master page and the content page will merge together at runtime—therefore, because the master page defines the <html>, <head>, <body> and <form> tags, you can easily guess that the content pages must not define them again. Content pages will only define the content for the master’s ContentPlaceHolder controls, and nothing else. The following extract shows an example of a content page:
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="MyPage.aspx.cs" Inherits="MyPage" Title="The Beer House - My Page" %>
<asp:Content ID="MainContent" ContentPlaceHolderID="MainContent" Runat="Server">
My page content goes here...
</asp:Content>
The first key point is that the @Page directive sets the MasterPageFile attribute to the virtual path of the master page to use. The content is placed into Content controls whose ContentPlaceHolderID must match the ID of one of the ContentPlaceHolder controls of the master page. In a content page, you can’t place anything but Content controls, and other ASP controls that actually define the visual features must be grouped under the outermost Content controls. Another point to note is that the @Page directive has a new attribute, Title, that allows you to override the value specified in the master page’s <title> metatag. If you fail to specify a Title attribute for a given content page, then the title specified on the master page will be used instead.
Figure 2-2 provides a graphical representation of the master page feature.
Figure 2-2
When you edit a content page in Visual Studio, it properly renders both the master page and the content page in the form designer, but the master page content appears to be “grayed out.” This is done on purpose as a reminder to you that you can’t modify the content provided by the master page when you’re editing a content page.
I’d like to point out that your master page also has a code-beside file that could be used to write some C# properties and functions that could be accessed in the .aspx or code-beside files of content pages.
When you define the ContentPlaceHolder in a master page, you can also specify the default content for it, which will be used in the event that a particular content page doesn’t have a Content control for that ContentPlaceHolder. Here is a snippet that shows how to provide some default content:
<asp:ContentPlaceHolder ID="MainContent" runat="server">
The default content goes here…
</asp:ContentPlaceHolder>
Default content is helpful to handle situations in which you want to add a new section to a number of content pages, but you can’t change them all at once. You can set up a new ContentPlaceHolder in the master page, give it some default content, and then take your time in adding the new information to the content pages—the content pages that haven’t been modified yet will simply show the default content provided by the master.
The MasterPageFile attribute at the page level may be useful if you want to use different master pages for different sets of content pages. If, however, all pages of the site use the same master page, it’s easier to set it once for all pages from the web.config file, by means of the <pages> element, as shown here:
<pages masterPageFile="~/Template.master" />
If you still specify the MasterPageFile attribute at the page level however, that attribute will override the value in web.config for that single page.
NOTE: This excerpt was taken from the book "ASP.NET 2.0 Website Programming". Click here to find more about it, and download the complete source code of the sample project.
 Sunday, January 29, 2006
I'm thrilled to announce that a few days ago I've finished reviewing all chapters, so I'm done at last! The book still needs some copy-editing work, but besides that it is 100% complete. It should be available around the end of February / beginning of March. I'll try to convince the Wrox guys to make the source code available even before than though, so that you can start playing with the sample site as soon as possible Below you find a short description for chapter 9, which was missing in the list from my previous post:
In Chapter 9 you’ll add a working e-commerce store with most of the essential features such as a complete catalog and order management system, persistent shopping cart, integrated online payment via credit cards, product ratings, managing product stock availability, rich-formatting of product’s descriptions including text and images, configurable shipping methods and order statuses, and much more. All this will be implemented in relatively few pages, since it will leverage the good foundations built in previous chapters, and of course the ASP.NET 2.0 built-in membership & profile systems, and other new features and controls such as the ubiquitous GridView, DetailsView, and ObjectDataSource, plus the Wizard and MultiView controls.
In the next days I'll update the "Marco's books" page (currently empty) with many details about the book, including the introduction, the description of all chapters, the full TOC, and links to download the source code. Also, I'm happy to anticipate that I've got the permission to publish on this site some short extracts from the book, possibly starting very soon! These pieces will be taken from the Design section of some chapters, where - in addition to designing modules such as newsletter, e-commerce, membership etc. - I introduce the new ASP.NET 2.0 features & controls used in that chapter, so that developers with no prior knowledge of ASP.NET 2.0 (knowledge of ASP.NET 1.x is required though) get the necessary background knowledge to understand everything in the chapter. Of course the level of details of these pieces is not as deep as in reference-books, however they provide enough information to get up to speed with the new technology, and build a very functional and professional Solution.
 Sunday, January 15, 2006
Ho appena pubblicato qui la versione aggiornata e feature-complete di TheBeerHouse, il sito di esempio per il libro ASP.NET Website Programming di cui ho parlato nell'ultimo post. L'ultima parte è stata implementare il modulo di commercio elettronico. Anche qui non ho voluto fare un esempio sempliciotto, ma ho inserito il supporto per categorie, stati dell'ordine, metodi di spedizione e valute multiple, vari attributi per i prodotti (percentuale di sconto, descrizioni formattate in HTML, immagine piccola e grande, elementi in magazzino ecc.), rating da parte dell'utente, shopping cart persistente anche per utenti anonimi (il login è richiesto solo in fase di completamento dell'ordine), storico ordini personali per controllarne lo stato, pagamento real-time con PayPal, ricerca e amministrazione completa degli ordini da parte di specifici utenti, gestione della disponibilità dei prodotti, e altro ancora.
L'implementazione di questo modulo ha richiesto 4 giorni di lavoro full-time (non proprio 8 ore al giorno...), che ritengo essere abbastanza pochi...soprattutto considerando che comunque non sono stati utilizzati DataSet/DataAdapter e tutti gli aiuti del designer di VS, ma è anche stato sviluppato un completo wrapping dei dati tramite classi DAL e BLL. Dove ASP.NET 2.0 veramente ha aiutato è stato nella gestione della UI però (oltre che nei servizi tipo Membership e Profile, che erano già stati configurati a dovere in precedenza, e che quindi qui non considero): sviluppare tutte le pagine dell'amministrazione, del catalogo e dello shpping cart avrebbero richiesto più di 2 giorni in ASP.NET, senza l'aiuto di controlli come GridView/DetailsView e l'ObjectDataSource di accompagnamento, che riducono drasticamente il codice C# nelle classi di code-beside. Non riesco neanche a pensare poi al confronto con ASP / PHP (che pure molti usando ancora per applicazioni nuove cominciate da zero).
Tirando qualche somma, il progetto completo è composto da 14 tabelle di DB (oltre a quelle usate da ASP.NET ovviamente), 98 stored procedure che incapsulano il 99% del codice SQL necessario, circa 130 classi C# per un totale di circa 15.000 righe, 50 pagine .aspx (di cui 20 di back-end, per amministrare praticamente tutto), 13 user control, 1 master page e 2 temi interscambiabili anche a runtime. Dal momento che ho dovuto lavorare parecchio in fretta è probabile che in giro siano rimasti dei bug...ed è per questo che rinnovo l'invito a provare il sito online, in attesa di poter scaricare tutto il codice e di provarlo in locale. Ora passerò i prossimi 4 gionri a scrivere il capitolo...ormai stò cominciando a pensare che potrei riuscire a sopravvivere 
Dato che mi è stato chiesto privatamente da più di qualcuno, dico anche qui che il libro in inglese dovrebbe essere disponibile a febbraio o giù di li. Per la versione italiana non so ancora se e quando...
 Tuesday, January 10, 2006
Yes, I know, it was 20 days ago that I said I would have published the link to the sample site developed for my "ASP.NET Website Programming - Problem Design Solution" book in a couple of days...but you know, there was Christmas, the New Year's Eve etc. etc. Anyway, here it comes: http://www.dotnet2themax.com/TheBeerHouse/
The following list outlines the current features, chapter by chapter:
- Chapter 1: Introduction
- Chapter 2: Design and implementation of the site's master page, multiple themes switchable by the user at runtime (see the Themes drop-down list at the top-right corner of any page), sitemap & menu system.
- Chapter 3: Discussions about a lot of stuff that goes under the name of "Site Foundations", such as choosing the data store, designing a configurable DAL based on the Provider Model design patter, using System.Transactions or Serverices Without Components for managing distributed transactions, caching strategies, health monitoring & exception handling, and more. Implementation of a number of base helper classes, and configuration classes to map custom sections in web.config.
- Chapter 4: Design and implementation of a full-featured membership and profiling system, and role-based security. A complete administration console is also built to allow administrators to see and modify the profile of any user, including blocking or deleting it, adding or removing roles to it, and more.
- Chapter 5: Design and implementation of a feature-rich module for publishing articles and dynamic content. It's like a mini-CMS, that allows you define categories and articles with support of rich formatting (through a WYSIWYG editor), generates site-wide RSS feeds or feeds for individual categories (a generic RSS reader is also provided), allows users to browse through paginated articles, post comments and rate the article. A complete administration console was built to completely manage the content from the browser, including moderating comments, protecting articles against anonymous users, setting the article's visibility date range and much more.
- Chapter 6: Design and implementation of a module for creating and managing opinion polls. There's the ability to create multiple polls and setting one as the current one displayed on the site's common layout, archiving past polls and deciding whether the archive is accessible to anonymous users or not, choosing whether the system checks for multiple votes by the user user by using cookies and/or IP, and more.
- Chapter 7: Design and implementation of a module for sending out newsletters and managing the archive, which can be public or not. The great thing here is that the module uses a multi-threaded procedure to send newsletters, and takes advantage of AJAX technology to provide real-time feedback of the completion status without refreshing the whole page.
- Chapter 8: Design and implementation of a forums system with support for multiple sub-forums that can optionally be moderated (by users with a tailor-made role), paginated thread list with different sorting options (by date, number of replies, number of views), configurable poster level, support for avatars, signatures and much more.
- Chapter 9: Design and implementation of an e-commerce store. TODO!!!
- Chapter 10: Integration of the Web Part Framework into the site, so that authenticated users can personalize the homepage (and a few more pages) by dynamically adding, removing, editing and moving around content boxes such a poll box, the feed of latest articles, latest forum discussions, most active threads, external RSS feeds, and more. Editors and administrators can edit a shared view, to change what the homepage contains - and how it displays the content - without actuallyediting the physical .aspx file and re-uploading it.
- Chapter 11: Complete localization of the site's common layout, the homepage, and the Web Part controls that can be placed on the personalizable pages. The supported languages are English and Italian, and the user can choose her language at registration time, or later from the Edit Profile page.
- Chapter 12: full pre-compilation of the site, and deployment on the remote server, with a SQL Server 2005 DB (instead of the Express DB using during development). An installer for distributable packages was also created.
As you see, the only thing left is just the e-commerce part, which I'll be developing in the next days. When complete, I think this will be one of the most complete ASP.NET 2.0 sites around, with really a lot of features and details that you don't usually find in "sample projects". Actually, I stopped calling this just a sample quite some time ago, as I consider it a real-world site. Implementing all this stuff gave me the opportunity to describe all new controls and features of ASP.NET 2.0 within a real context, and I believe this is more useful than simple one-page examples that show new features one-by-one without integrating them into a larger project.
If you're interested in the book and the site developed, please give a look at the site online and have a tour of it...and if you have any feedback please don't be shy, your comments will be seriouly considered!
|
|
Get RSS/Atom Feed
Search in the blog
Archive
| | Sun | Mon | Tue | Wed | Thu | Fri | Sat | | 30 | 1 | 2 | 3 | 4 | 5 | 6 | | 7 | 8 | 9 | 10 | 11 | 12 | 13 | | 14 | 15 | 16 | 17 | 18 | 19 | 20 | | 21 | 22 | 23 | 24 | 25 | 26 | 27 | | 28 | 29 | 30 | 31 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
Categories
Powered by: newtelligence dasBlog 1.8.5223.1
|