 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();
|