Marco Bellinaso's Blog

 Tuesday, June 20, 2006

My editor has just approved the addresses of the five people that I've randomly picked from those who partecipated to the book giveaway contest. Here they are:

  1. Mel Grubb II (Columbus, OH)
  2. Mohee Uddin (New York, NY)
  3. Andrew Norman (Sarasota, Fl)
  4. Kyle Nash (Franklin, TN)
  5. Igor Antonacci (Italy)

Congratulation guys, your books have already been ordered, and should be soon on their way to your home. I hope you'll enjoy reading the book :-)
There were about one hundred of you that submitted their info for the contest, so I'm afraid 95% won't receive the free book...however, if you really want to read the book anyway, I suggest you purchase it on Amazon, because you have a 30% off the regular price! 25 bucks is not that bad for a 600-pages book, I think.

6/20/2006 3:59:14 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [16]  | 
 Friday, June 09, 2006

The book giveaway contest is having a very good response, and I've already received many dozens of submissions. Because of this, I decided that one more week is already enough time. So, you can send your submission (full address and job description) within June 15th...I'll announce the winners soon after that. Thanks for your interest :-)

6/9/2006 2:02:30 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [16]  | 
 Wednesday, May 31, 2006

My great acquisitions editor at Wrox, Jim Minatel, just wrote me an e-mail saying he has no less than 5 copies of my ASP.NET 2.0 Website Programming book to give away to some of you, blog readers :-) All we ask in return is a review on Amazon (and possibly on your blog, if you have one). Please note that we're asking for a 100% honest review...if you don't like something in the book, you will be absolutely free to express it. You can rest assured we're not looking for biased reviews and comments. So, if you're interested, please contact me and specify the following information:

  • your complete name and physical address
  • if you're outside the USA please include also your phone number
  • a few lines to describe your job, and your experience with web development and ASP.NET 2.0

I'll collect your information for a few weeks (how many depends on the number of mails I receive...it may be 2 weeks or a month) and then randomly pick 5 of you. I'll then publish the names of the winners on the blog, and pass the address information to Jim at Wrox so that he can send the books. I hope you appreciate the initiative :-)

Good luck!

5/31/2006 11:17:13 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [15]  | 


If you deploy the site by copying all the files (including the source code), the pages and the source code files are compiled dynamically at runtime when they are first requested by a user. This is called in-place compilation, and the generated assemblies are compiled into a temporary folder. As an alternative, instead of deploying source files, you can use the aspnet_compiler.exe tool (located under C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727) to pre-compile the source code files and (optionally) the markup files. This is the command you could use to pre-compile everything (this should all be entered on one line):

aspnet_compiler.exe -p c:\Projects\ASP.NET\TheBeerHouse\TBH_Web -v /TheBeerHouse c:\Deployment\TheBeerHouse

The -p parameter specifies the source directory, and the -v parameter specifies the virtual directory used at runtime by the site. The path at the end of the command is the destination directory for the compiler output files. If you look under the c:\Deployment\TheBeerHouse\bin folder, you’ll find multiple .dll assembly files, plus one .compiled XML file for each .aspx and .ascx file (see Figure 12-15).

Figure 12-15

The files named with a .compiled extension contain XML text that shows the relationship between the virtual path of a page or user control and the corresponding type compiled into one of the assemblies. If you look into any of the .aspx files, you won’t find any HTML/ASPX markup code, but rather the markup string printed below:

This is a marker file generated by the precompilation tool, and should not be deleted!

After executing the aspnet_compiler, you take all the output generated by this tool and upload it to the remote server, typically via FTP. If you’re deploying to a server within your network you might simply copy the files to a shared folder on that server. However, even local servers are often isolated behind a firewall, so FTP may be needed anyway.

Compiling the markup code may definitely be appealing in some circumstance, such as for packaged commercial products for which you don’t want the client to change anything; however, if you’re deploying to your own site this may not be particularly useful or necessary. Furthermore, it would complicate updates, because every time you need to change a line of markup you’d have to recompile everything and re-deploy the generated files. In the case of deploying to your own sites, it’s simpler to pre-compile only the source code files but not the markup files. To do this, just add the -u switch (which stands for updateable) to the command line, as follows:

aspnet_compiler.exe -p c:\Projects\ASP.NET\TheBeerHouse\TBH_Web -v /TheBeerHouse -u c:\Deployment\TheBeerHouse

With this command no .compiled files will be generated under the /bin folder, and the content of the .aspx and .ascx files won’t be removed. However, the CodeFile attribute of the @Page and @Control directives will be removed, and the Inherits will be updated with a reference to the type compiled into one of the generated assemblies.

Note that all static files (images, .htm files, .css stylesheet files, etc.) are always copied “as is” to the target folder. These are never included as part of a pre-compile.

However, there’s a small deployment issue when using one of the two pre-compile commands described above: The assemblies they generate always have a different name, which makes it difficult to update the site locally and then replicate the changes remotely, because the assembly names will be different after each pre-compile. If you don’t want to leave old and unused assemblies in the remote /bin folder, you need to delete them first and then upload all new .dll files. This is very annoying and time-consuming, so you can add the -fixednames compiler switch to cause the aspnet_compiler to create an assembly for each file it compiles, using a fixed name scheme. This is good because it allows you to update the site locally, recompile it, and then upload only the changed assembly file. This is the modified command line:

aspnet_compiler.exe -p c:\Projects\ASP.NET\TheBeerHouse\TBH_Web -v /TheBeerHouse -u -fixednames c:\Deployment\TheBeerHouse

I covered the syntax of the command-line tool for completeness (and because many of you will want to script this procedure), but you don’t have to remember all the various switches because Visual Studio provides a simple integrated UI for aspnet_compiler, which you can access by clicking Build @@> Publish Web Site. Figure 12-16 shows the graphical front-end that it provides, making it easy to select your options, and to select a local or remote IIS site, or an FTP site, as the destination for the operation, in addition to a local folder.

Figure 12-16

Some of you may be concerned about the large quantity of assemblies produced by the pre-compilation step. In large projects with hundreds of pages, the /bin folder will contain a lot of files, and it may be more convenient for deployment if you could combine all those .dll files into a single assembly file. There’s no way to do this with the tools included in VS2005 and the standard installation of the .NET Framework 2.0, but Microsoft listened to its customers, and after releasing VS2005, they later released a free package called “Visual Studio 2005 Web Deployment Projects,” which can be downloaded from http://msdn.microsoft.com/asp.net/reference/infrastructure/wdp/default.aspx. After installing the package, you’ll find a new command-line tool called aspnet_merge.exe under the C:\Program Files\MSBuild\Microsoft\WebDeployment\v8.0 folder. As its name suggests, it enables you to merge the multiple assemblies generated by the aspnet_compiler tool into a single dll. When you run this program you only need to specify the path of the pre-compiled web site where it can find the assemblies you want to merge:

aspnet_merge.exe c:\Deployment\TheBeerHouse

The preceding command generates a dll for each of the web site’s folders containing files that were pre-compiled by aspnet_compiler. This is useful when you have folders that include a sub-application supported by different developers (such as the administration console), and you want to have separate assemblies for separate sections so that you can update them independently on the production server. In other cases, however, you may prefer to merge everything into a single assembly: You can do so with the -o switch, which specifies the name of the assembly being generated:

aspnet_merge.exe c:\Deployment\TheBeerHouse -o MB.TheBeerHouse.dll

Note that the tool can be used whether the aspnet_compiler.exe pre-compiled the markup code or not. But this tool never merges in any external libraries referenced by the web site’s source files and pages, such as the MB.TheBeerHouse.CustomEvents.dll and the FredCK.FCKEditorV2.dll assemblies. It only merges assemblies generated by aspnet_compiler.exe.

As for aspnet_compiler, there’s also a front-end UI for aspnet_merge. The Visual Studio 2005 Web Deployment Projects package is installed as an add-in for VS2005, and it adds a new project type called a “Deployment Project.” You add a new deployment project to your solution by clicking the Add Web Deployment Project… option on the IDE’s Build menu, or via the same option on the context menu of the web site in the Solution Explorer window. You create the project by choosing its name and location from the dialog box shown in Figure 12-17.

Figure 12-17

The project added to the Solution Explorer contains no files; you have to double click the project name to open its configuration dialog box, from which you can specify a number of options. Among other things, you can specify how you want the pre-compiler to work, and how you want the files merged. In the first tab of the dialog box (see Figure 12-18), you specify the output folder, and choose whether the user interface pages and controls should be pre-compiled (which is the updateable option, corresponding to the -u switch of the aspnet_compiler.exe tool).

Figure 12-18

In the second tab, Output Assemblies (see Figure 12-19), you specify whether you want to compile everything into a single assembly, have an assembly for each folder, have an assembly for the user interface pages and controls, or have an assembly for each class and page being compiled (this last option means you don’t want to use aspnet_merge.exe). From here you can also specify the version information of the generated assemblies. If this information is not provided, the settings specified in the web.config file located under the /App_code folder will be used instead (if the file is present, which is not a requirement).

Figure 12-19

The third tab, Signing (not shown here), enables you to sign the generated assemblies with a key file generated by the sn.exe command-line tool, to give them a strong name. This isn’t normally desired for your own sites, but may be useful when you’re creating a packaged application and you want to ensure that your assemblies will not be tampered with. In the last tab, Deployment (see Figure 12-20), you can choose to replace one or more sections of the site’s web.config file with the content from another file. For example, if you write connectionStrings=connectionStrings.LocalSql.config, the whole <connectionStrings> section will be replaced with the content of the connectionStrings.LocalSql.config file at the end of the build process. This enables you to have a connection string pointing the SQL Server Express database to be used while testing the site locally, and later have it automatically replaced with a connection string referencing a local or remote SQL Server 2005 database after building the project for deployment. You can specify additional sections to replace, one per line. You can also use this window to specify a virtual directory to be created during the build process, and whether the App_Data folder will be deleted from the files generated (useful when you will use a SQL Server 2005 database after the build, in which case you do not want to deploy your express files under App_Data).

Figure 12-20

Once you’ve completed the configuration, you can build the project. At the end of the build process you’ll find a copy of the site with the pre-compiled and merged assemblies, plus all other files such as pages, controls, images, stylesheets, and so on in the output folder. You can then take this entire output and upload everything to the production server, typically via FTP.

Deployment projects simply consist of an XML file used to pass options to MSBuild.exe, the new Microsoft build tool capable of compiling and building complex projects and solutions. MSBuild is an extensible tool that uses configuration files that can contain many different options. And if an option or a task that you’d like doesn’t exist yet, you can create it as a C# class, and have MSBuild call it. Many of the configuration settings described here were implemented as custom settings and tasks by the developers of the Web Deployment Projects add-in. There are many more available options in addition to those you can configure from the Properties window explored earlier, such as the option to exclude files from the build, create new folders, grant the ASP.NET account write access to a folder (only on your local machine, though—you’ll still need to replicate these security settings on the remote production server), execute external programs, and much more. You can add more settings and tasks (i.e., operations to run before or after the build and the merge processes) directly from the XML configuration file, which can be opened by clicking the Open Project File option from the Web Deployment project’s context menu. Covering MSBuild is beyond the scope of this book, but you can find a lot of good documentation about this on the web, and in the documents that come with the Web Deployment Projects package.

By default, all projects added to the solution are built in Visual Studio when you launch the primary project, i.e., the web site. Building the deployment project takes quite a lot of time, however, depending on the size of the site (on my machine it takes around 30 seconds to generate the pre-compiled site), and it is not something you want to do while testing the site locally. In order to avoid this waste of time, you can just exclude the project from the Build | Configuration Manager… dialog box.  


 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.

5/31/2006 10:36:18 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [5]  | 
 Tuesday, May 09, 2006


You can create page-level resources by creating resource files just as you do for global resources, but placing them under a folder named App_LocalResources (as opposed to App_GlobalResources used for global resources) located at the same level of the page to localize. For example, if the page is in the root folder, then you’ll create the App_LocalResources under the root folder, but if the page is under /Test, then you’ll create a /Test/App_LocalResources folder. This means you can have multiple App_LocalResources folders, whereas you can only have one App_GlobalResources folder for the whole site. The name of the resource filename is also fundamental, as it must be named after the page or control file for which it contains the localized resources, plus the part with the culture name: For example, a culture-neutral resource file for Localization.aspx would be named Localization.aspx.resx, whereas it would be named Localization.aspx.it-IT.resx for the Italian resources. In Figure 11-5, you can see the organization of files in the Solution Explorer, and the resource file being edited in the grid editor.

 

Figure 11-5

You can still use the Expressions dialog shown in Figure 11-4 to bind a control’s property to an expression pointing to a localized resource: When you point to a page-specific resource you just leave the ClassKey textbox empty. The code below shows the generated expression:

<asp:Label ID="lblCopyright" runat="server"

   Text="<%$ Resources:CopyrightMessage %>" / >

It differs from the expression shown in the previous section, as it doesn’t include the class name; it just specifies the resource key. If you want to access local resources programmatically, you use the page’s GetLocalResourceObject method, which takes the resource key name and returns an Object that you must cast to string or to the proper destination type (such as Bitmap if you stored an image):

string copyrightMsg = (string)this.GetLocalResourceObject("CopyrightMessage");

Even with localization expressions and local resources, localizing full pages will be a slow task if you need to create the expressions for dozens of controls, and things get worse if you need to localize multiple properties for the same control, such as Text, ToolTip, ImageUrl, NavigateUrl, and so on, which is often the case. To speed things up, Visual Studio offers the Tools @@> Generate Local Resource command, which generates a local resource file for the current page, and creates entries for all localizable properties of all controls on the page, following the ControlName.PropertyName naming convention for the names of the resources. Resource items are also automatically set with the value extracted from the page’s markup; if a property is not used in the control’s declaration, a resource item for it is generated anyway and left empty.

Localizable properties are those that are decorated with a [Localizable(true)] attribute, which you can add to the properties of your custom controls. However, even when you add it to properties of user controls, resources for those properties will not be automatically generated by the Generate Local Resource command. You can create the local resources for those properties yourself, and write the localization expressions to make the association: Everything will work perfectly at runtime, so this is only a design-time limitation. In addition, you’ll have to write the localization expressions manually, because the (Expressions) item is not available from the Properties window when a user control is selected.

Figure 11-6 shows the resource editor for the Localization.aspx.resx local resource file after executing this Generate Local Resource command on the test page.

Figure 11-6

Besides the automatic generation of the resource file (or the addition of the resource items, if a resource file for that page was already present in the proper folder), what’s even more interesting is that each control’s declaration is modified as follows (note the code in bold):

<asp:Label ID="lblTitle" runat="server" Font-Size="X-Large" ForeColor="#C00000"

   meta:resourcekey="lblTitleResource1" Text="Localization Demo"

   Text="This page provides a nice demo of new ASP.NET 2.0 localization features"

/>

A meta:resourcekey attribute is added to the declaration, and is set to the prefix used in the local resource file to identify all localized properties of that control, such as lblTitleResource1.Text and lblTitleResource1.ToolTip. These are called implicit localization expressions (expressions used earlier are considered explicit). At runtime, the framework parses all resources and applies them to the properties of the corresponding controls, making the mapping of the first part of the key with the value of meta:resourcename. This means that the control’s declaration is decorated with just a single new attribute, but may make multiple properties localizable. Later, if you want to localize a property that you didn’t take into account originally, you just need to go to the resource editor and add an item following the naming schema described above, or edit its value if it already exists.

Note that the controls retain their original property declarations after running the Generate Local Resource command. These declarations are no longer necessary, though, as the property’s value will be replaced at runtime with the values saved in the resource file; therefore, you can completely remove the definition of the Text, ToolTip, and the other localized properties from the .aspx files to avoid confusion.

This behavior works with the page’s title as well, originally defined in the @Page directive, which is modified as follows:

<%@ Page Language="C#" meta:resourcekey="PageResource1" …other attributes… %>


 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.

5/9/2006 7:29:18 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [14]  | 
 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.

5/1/2006 12:11:23 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [24]  | 
 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.

4/22/2006 9:29:13 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [6]  | 


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.

4/22/2006 9:25:01 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
 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.

4/13/2006 8:07:04 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [4]  | 
 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.

ADO.NET Transactions

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.

COM+ and SWC Transactions

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.

The new System.Transactions Namespace

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.

4/7/2006 10:13:45 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
 
Get RSS/Atom Feed
RSS 2.0 | Atom 1.0
Search in the blog
Archive
<June 2006>
SunMonTueWedThuFriSat
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678
Categories

Powered by: newtelligence dasBlog 1.8.5223.1