All that remains is to create the new instance of the WorkflowDesigner, and insert it into the application:Imports System.Activities Imports System.Activities.Core.Presentation Imports S
Trang 1➤ Activities that communicate with external code — These activities are either called by external
code to initiate a workflow or used to call to external code as part of a workflow This category also includes activities that communicate with external systems to persist the workflow state
Receive Receives a one-way WCF message
ReceiveAndSendReply Receives a WCF message and sends back a result
SendAndReceiveReply Sends a WCF message and waits for a result
Persist Saves the current state of the workflow This is very useful for long-running
workflows, as it enables you to save the current state of the workflow, saving memory You can then reload the workflow as it was persisted as needed later
CancellationScope Marks the boundaries of a set of activities to perform if a process is cancelled
Typically, this would be used to close any handles, undo any partially completed steps, etc
CompensableActivity Marks the boundaries of an activity that may be “undone ” This activity groups one
or more actions to be performed In addition, it contains actions to undo whatever steps may have already been performed This is typically to enable rollback of a partially failed transaction This activity is used as an alternative to transactions when you don’t necessarily control the success of each of the steps in a process For example, if you send a request to a Web service, and then fail another step, the CompensableActivity can send a cancel request to the Web service Compensate Invokes the compensation activity in a CompensableActivity activity That
is, it “undoes” whatever activity was performed Confirm Performs the equivalent of a commit on the CompensableActivityTransactionScope Marks the boundaries of a transaction within the workflow
Rethrow Rethrows an existing exception This is typically done within the Catch clause of a
Try Catch activity to propagate the exception to another part of the workflow Throw Creates an exception within a workflow
TryCatch Wraps an activity (use a sequence if you need multiple children) within a
Try Catch block to handle exceptionsCorrelationScope Marks the boundaries of a set of Web services that will share a correlation handleInitializeCorrelation Allows you to initialize a correlation Typically, this is done using a message,
but this activity allows you to start it without an explicit correlation message TransactedReceiveScope Allows you to flow a transaction into a WCF communication
➤ Transaction activities — These activities group a number of other activities together into some logical
element This is usually done to mark a number of activities that participate in a transaction
Switch<T> Works like the VB case statement Switches the flow through a workflow based
on the value of a variable or condition While Works like the VB while…end while loop Performs a child activity (use a
sequence if you need multiple steps) while a condition is true TerminateWorkflow Stops the workflow before the end of the workflow is reached This is useful
in the event of errors in the workflow, or if the data input doesn’t allow for completion Also used for flowchart workflows as a means of completion
(continued)
Trang 2➤ Flowchart activities — These activities are used in flowchart-style workflows and allow for the
organization of the steps, simple decisions, and other stages
Flowchart This activity is used to create a flowchart workflow It is a container for all the steps
involved in the workflow FlowDecision A simple If statement within a flowchart workflow This is used to control the actions of a
workflow based on a condition FlowSwitch A switch statement within a flowchart workflow This works similar to the VB case
statement in that you have multiple cases that work based on the assigned condition You also define a default condition if none of the cases apply
a less simple Workflow
To see a few of these activities together, create a new Workflow Console Application named Fulfillment
This will be used to create part of a workflow for
an order fulfillment application The workflow will collect an XML file from a directory on disk, validate
it using a few simple rules, and add it to a collection representing the order queue Other workflows might then retrieve items from this collection for actual processing Figure 26-13 shows the final workflow
As you can see from the figure, the workflow is a flowchart consisting of four stages The DisplayName property of each of these stages has been set to better describe the contents of the stage As you would expect, this is invaluable in improving the understanding of the workflow when you come back
to it later (or try to explain it to end users) The basic outline of the workflow is as follows:
The workflow begins a loop to monitor
If the order is valid, it is added to a collection for later processing If not, the validation errors are
➤displayed and the workflow completes This is an If activity
To demonstrate additional processing, the orders collection is simply displayed to the console Of
➤course, in a real application, this stage would send the orders on to another application for actual fulfillment and shipping This is a ForEach<T> activity
Before you begin building the workflow, there are some helper classes that you need to build These represent an order, an order detail line, and a manager class for processing the order Add a new Class
Library project to the solution, named OrderManager This has three classes: Order, OrderDetail, and
figure 26-13
Building Workflows ❘ 889
Trang 3Public Property Details As List(Of OrderDetail)
Public Sub New() Details = New List(Of OrderDetail) End Sub
End Class
Code snippet from OrderManager
The OrderDetail class is an individual line item within an order Again, for this example it is greatly simplified:
Public Class OrderDetail Public Property Parent As Order Public Property ItemName As String Public Property Quantity As Integer End Class
Code snippet from OrderManager
The OrderSystem class is a general manager class for the orders In addition to the functionality for this demo, it would likely be responsible for saving orders to a database, and so on:
Public Class OrderSystem
Public Function GetOrderFromDropFile(ByVal path As String) As Order Dim result As Order = Nothing
Dim files As String() Dim doc As New XDocument Dim detail As OrderDetail
files = IO.Directory.GetFiles(path)
If files.Length > 0 Then doc = XDocument.Load(files(0)) 'load header
result = New Order With result .OrderID = CInt(doc.Root.Attribute("id").Value) .CustomerName = doc.Root.Element("customerName").Value .OrderDate = CDate(doc.Root.Element("orderDate").Value) .ShipAddress = doc.Root.Element("shipAddress").Value End With
'load detail rows Dim details As List(Of XElement) = (From item In doc.Descendants Where item.Name = "orderDetail"
Select item).ToList
For Each d In details detail = New OrderDetail With detail
Parent = result .ItemName = d.Element("itemName").Value .Quantity = CDec(d.Element("quantity").Value) End With
result.Details.Add(detail) Next
'delete file to avoid calling this again 'likely you would move to a backup directory instead ' IO.File.Delete(files(0))
End If Return result
Trang 4End Function Public Function ValidateOrder(ByVal anOrder As Order) As String() Dim result As New List(Of String)
'check for OrderID
If Not IsNumeric(anOrder.OrderID) Then result.Add("Order ID is not valid") End If
'check for ship address
If Not String.IsNullOrEmpty(anOrder.ShipAddress) Then result.Add("No ship address")
End If 'check for at least one OrderDetail
If anOrder.Details.Count < 1 Then result.Add("Must have at least one item in order") End If
'other checks here
Return result.ToArray End Function
End Class
Code snippet from OrderManager
For this example, the OrderSystem class exposes two methods The first attempts to load an XML file from an assigned directory Once a file has been loaded, it converts the contents of the XML file into
an Order object, and one or more OrderDetail objects LINQ to XML is used to retrieve the rows containing order details
The second method does a few simple validations on the order, and returns a list of validation errors (as strings) to the calling program
The following code shows a sample order XML file (also included in the source code for the OrderManager project):
<?xml version=”1.0” encoding=”utf-8” ?>
<order id=”1234”>
<orderDate>2009-12-01</orderDate>
<customerName>Moe’s Family Diner</customerName>
<shipAddress>1313 Mockingbird Lane, Springfield, AK</shipAddress>
Trang 5Build the project to ensure you have no errors, and then you’re ready to build the workflow to use these classes Add a new Flowchart activity to the designer, and add the four activities shown in Figure 26-12, connecting them as shown.
The workflow will make use of the objects in the
OrderManager project As such, you should import
that namespace into your workflow First, add a
reference to the OrderManager project: Right click on
the Fulfillment project and select Add Reference
Select the OrderManager project on the Projects tab
Next, click the Imports link at the bottom of the
workflow designer on the FulfillmentWorkflow
This displays the current list of namespaces available
to your workflow Add the OrderManager namespace
by entering it in the space at the top of the list and
pressing Enter to save it to the list
The DoWhile loop consists of a Sequence, which in
turn contains a Delay activity and an InvokeMethod
activity (see Figure 26-14) The DoWhile activity
requires that you set a condition that will end the
loop In this case, it will be when an order has been
picked up by the InvokeMethod
The following table describes the property settings
for the added activities
figure 26-14
DoWhile Condition theOrder Is Nothing You will create the theOrder variable
shortly This variable will hold an instance of an Order class for processing
TimeSpan In this case, the delay is set for 10 seconds In a real-world application, you would set this based on the frequency
of orders being processed InvokeMethod TargetObject manager This is an instance of an
OrderSystem class MethodName GetOrderFromDropFile A method on the OrderSystem classResult theOrder Once a new file has been processed,
the resulting order is saved for processing within the workflow Parameters System
.Configuration.ConfigurationManager.AppSettings(“dropFilePath”).ToString()
The directory to monitor will be set using the application configuration file
The InvokeMethod activity is used to call the ValidateOrder method on the manager object Set the properties on this activity as shown in this table:
Trang 6figure 26-15
InvokeMethod TargetObject manager This is an instance of an OrderSystem class
MethodName ValidateOrder A method on the OrderSystem classResult ValidationErrors A variable that will be added to the
workflow shortlyParameters theOrder The instance of the Order class created by
the GetOrderFromDropFile method above
that will store the orders Item theOrder The item to add to the collection In
this case it is a workflow variable ForEach<T> TypeArgument String This will iterate over each of the
items in the ValidationErrors collection to display them Values ValidationErrors This is the collection to iterate over WriteLine Text ValidationError This is the value of the current
iteration in the loop TerminateWorkflow Reason “One or more
orders have errors”
This will be available to the calling application to determine why the workflow terminated
Next, the processing branches based on whether errors are encountered in the order If the order is valid, then it is added to a collection for further processing If, however, there are any validation errors, they are displayed and the workflow ends (see Figure 26-15) Set the properties for these activities as follows:
Trang 7Finally, the orders are simply displayed on the console to confirm they have been processed This is done with another ForEach<T> activity that writes the order’s information, followed by each of the detail rows in the order (see Figure 26-16) The properties of these activities are defined as follows:
ForEach<T> TypeArgument OrderManager.Order This will iterate over each of the orders
in the collection to display them Values Orders This is a workflow variable containing
the orders submitted
.Format(“Order on {0}
by {1} for:”, item OrderDate, item CustomerName)
Displays the contents of the order’s header information
ForEach<T> TypeArgument OrderManager
.OrderDetails
This will iterate over the detail rows contained within the submitted order
Values item.Details This is the collection of order details
within the current order
.Format(“{0} {1}(s)”, detail
.Quantity, detail ItemName)
Displays the contents of the fields of each order detail row
figure 26-16
Trang 8VariaBle TyPe descriPTion
theOrder OrderManager.Order Will hold the current submitted orderOrders List<Order> Represents the current queue of orders for processing
Set the default to New List(Of Order) to ensure that the collection is initialized
ValidationErrors String() Will hold any validation errors in the current submitted
order
manager OrderManager.OrderSystem Will hold the object that provides the processing for
the loading and validating of the orders
As described above, you will use a number of workflow variables needed to store data during processing These are described in the following table:
All that remains is to update the host application As described above, you will provide an instance of the OrderSystem class to the workflow This is done in the Main method for the Console application:
Shared Sub Main() Dim inputs As New Dictionary(Of String, Object) 'Workflow expects the OrderSystem as parameter Dim sys As New OrderManager.OrderSystem inputs.Add("manager", sys)
WorkflowInvoker.Invoke(New FulfilmentWorkflow(), inputs)
Console.WriteLine("Press ENTER to exit") Console.ReadLine()
End Sub
Code snippet from Fulfillment
Recall that the input for a workflow is a Dictionary(Of String, Object), and that the key in this dictionary must match the name of an argument in the system — in this case, manager
Before running the application, you also need to add an application configuration file This will include a single application setting named dropFilePath that should be set to the location where you will add the XML files
Run the application and copy an XML file to the monitored directory After a brief delay, you should see the contents of the order displayed on the console (see Figure 26-17)
figure 26-17
Building Workflows ❘ 895
In addition to those variables, an instance of the OrderSystem class will be passed into the workflow as an argument Open the Arguments pane and add the following item
Trang 9Building custom activities
In addition to the standard activity library, WF supports extensibility through the creation of custom activities Creating custom activities is a matter of creating a new class that inherits from Activity (or one
of the existing child classes) Creating custom activities is the primary means of extending WF You might use custom activities to simplify a complex workflow, grouping a number of common activities into a single new activity Alternatively, custom activities can create a workflow that is easier to understand, using terms that are more familiar to the developers and business experts Finally, custom activities can be used to support software used within the business, such as activities to communicate with a Customer Relationship Management (CRM) or Enterprise Resource Planning (ERP) system
Creating custom activities with WF 4 is much easier than it was with earlier releases To create a custom activity, you inherit from Activity, or one of the existing children of Activity, and override the
appropriate methods The most common classes you will inherit from are as follows:
➤ Activity — The base class Use only if one of the other following three classes are too specific for your needs
➤ CodeActivity — Use when your activity performs some action You override the Execute method
to carry out your action This activity works synchronously (as opposed to the AsyncCodeActivity below), so the entire activity must complete before the workflow continues
➤ AsyncCodeActivity — Similar to CodeActivity, but the work is performed asynchronously This
is the most commonly used class to inherit from when creating custom activities
➤ NativeActivity — Use this when your activity needs to interact with the workflow engine itself For example, the flow control activities inherit from this class
When defining properties for your custom activities, you do not use the standard types Instead, you use a generic class to wrap the type This enables your properties to communicate with the running workflow There are three wrappers you should use in your activities:
➤ InArgument(Of type) — Used to wrap a property that will be provided to the workflow
➤ OutArgument(Of type) — Used to wrap a property that the workflow will expose to the calling code
➤ InOutArgument(Of type) — Used to wrap a property that will be provided to the workflow, as well
as returned
To see how you can easily create a new activity and use it within a workflow, create a new Workflow Console application (CustomActivity) Add a new class (EncryptActivity) to the project for your new activity This new activity will be used to encrypt a string within a workflow (you’ll also be creating
an activity to decrypt the text):
Imports System.Activities Imports System.Security.Cryptography Imports System.Text
Public Class EncryptActivity Inherits CodeActivity
Public Property Input As InArgument(Of String) Public Property Password As InArgument(Of String) Public Property Output As OutArgument(Of String)
Protected Overrides Sub Execute(ByVal context As CodeActivityContext) Dim aes As New AesCryptoServiceProvider
Dim hash As New MD5CryptoServiceProvider
'load the properties from the current workflow context Dim plaintext As String = Input.Get(context)
Trang 10Dim pwd As String = Password.Get(context)
Dim inBuffer As Byte() Dim outBuffer As Byte()
'the key is the input to the encryptor 'we can only decrypt using the same password aes.Key = hash.ComputeHash(Encoding.ASCII.GetBytes(pwd)) 'Electronic CodeBook format (each block is encrypted individually) aes.Mode = CipherMode.ECB
Dim encrypt As ICryptoTransform = aes.CreateEncryptor inBuffer = Encoding.ASCII.GetBytes(plaintext)
'here's the actual encryption outBuffer = encrypt.TransformFinalBlock(inBuffer,
0, inBuffer.Length)
'store the output in the current workflow context 'Base64 to avoid any high ASCII issues
Output.Set(context, Convert.ToBase64String(outBuffer))
End Sub End Class
Code snippet from CustomActivity
The encryption uses the AES encryption, although you could use any of the encryption methods in the System.Security.Cryptography namespace You can see Chapter 34 for more details on the classes in this namespace, but the mechanics of using them are as follows:
1. Create an instance of one of the cryptography service providers
2. Set the Key (and optionally IV, or initialization vector, properties) on the service provider This is the value used to provide the encryption (i.e., the password)
3. Create an actual encryptor using the service provider
4. Encrypt the text Note that the encryption method (TransformFinalBlock) does not take a string, but
an array of bytes, so you need to convert your input (and output)
Add another class (DecryptActivity) to the project The code for the DecryptActivity is basically a mirror image of the EncryptActivity:
Imports System.Activities Imports System.Security.Cryptography Imports System.Text
Public Class DecryptActivity Inherits CodeActivity
Public Property Input As InArgument(Of String) Public Property Password As InArgument(Of String) Public Property Output As OutArgument(Of String)
Protected Overrides Sub Execute(ByVal context As CodeActivityContext) Dim aes As New AesCryptoServiceProvider
Dim hash As New MD5CryptoServiceProvider
'convert the input parameters from the current context Dim encryptedtext As String = Input.Get(context) Dim pwd As String = Password.Get(context)
Dim inBuffer As Byte()
Building Workflows ❘ 897
Trang 11Dim outBuffer As Byte()
'generate security hash from the password aes.Key = hash.ComputeHash(Encoding.ASCII.GetBytes(pwd)) aes.Mode = CipherMode.ECB
'create decryptor Dim decrypt As ICryptoTransform = aes.CreateDecryptor inBuffer = Convert.FromBase64String(encryptedtext)
'do actual decryption outBuffer = decrypt.TransformFinalBlock(inBuffer, 0, inBuffer.Length)
'Save the decrypted text to the current workflow context Output.Set(context, Encoding.ASCII.GetString(outBuffer))
End Sub End Class
Code snippet from CustomActivity
The main difference between the two activities is that rather than create an
encryptor, you create a decryptor In addition, because the output of the
encryptor was converted to a base 64 string, it is converted to a byte array using
FromBase64String
New activities will not appear in the Toolbox until they have been compiled, so
build the project to ensure that everything is working Once you have done that,
you can build your workflow to test the two activities Switch to the workflow
designer You should see the new activities in the Toolbox (see Figure 26-18)
Drag a Sequence activity onto the designer, and then add an EncryptActivity,
DecryptActivity, and WriteLine activity to it The final workflow should
look like Figure 26-19
The input parameters will be provided by the host console application To do
this, you need to configure the workflow with the desired parameters You then
provide them to the workflow by including a Dictionary containing those
parameters Click the Arguments link on the workflow designer You will use
two input parameters (for the text to encrypt and the password) and an output
parameter (for the decrypted text) The names of these parameters do not
need to match the properties of the custom activities, but the case is significant
in the Dictionary, so you need to ensure that they are added correctly (see
Now you’re ready to set the properties for the activities The following table shows how they should be set:
Trang 12You can now turn your attention to the main routine that will call the workflow The input to the workflow and the output of the Invoke method are both of type Dictionary(Of String, Object) The key you use
to add the item to the Dictionary is important, as it should match the names of the arguments you added to the workflow, including the case of the name The following code shows the Main method of the console application:
Shared Sub Main() Dim parms As New Dictionary(Of String, Object) Dim output As New Dictionary(Of String, Object)
'add the input parameters parms.Add("inputText", "Some text to encrypt") parms.Add("password", "5up3r53cr3t!")
Console.WriteLine("The original text is: {0}", parms.Item("inputText").ToString())
output = WorkflowInvoker.Invoke(New Workflow1(), parms)
Console.WriteLine("The decrypted string is: {0}", output.Item("outputText").ToString())
Console.WriteLine("Press ENTER to exit") Console.ReadLine()
End Sub
Code snippet from CustomActivity
You could reuse the Dictionary for both input and output, but in this case two dictionaries are created
to avoid any confusion The two input parameters are added to the input dictionary, keeping in mind that case is significant and should match the arguments you created earlier on the workflow This input dictionary is added as a parameter in the call to the WorkflowInvoker.Invoke This also populates the output dictionary with any OutArgument arguments of the workflow — in this case, the outputText value Running this workflow should display the same text for input and output
dynamically loading Workflows
As each workflow is a self-contained block of XAML, you might want to dynamically load your workflows, rather than compile them into the application This gives you easier access to changing or extending your application by creating or editing the XAML and making it available to your application
In order to have the XAML files left “loose” when you compile them, you need to change the properties for the XAML file Select the workflow file in the Solution Explorer and set the Build Action to Content and
Trang 13the Copy to Output Directory to Copy if newer This will move the XAML files to the output directory when you build the application The DynamicallyLoadingWorkflows sample includes three sample workflows.
To load one of the workflows, you use the ActivityXamlServices.Load method This is from the System Activities.XamlIntegration namespace The method takes a path to a XAML file and loads it You can then pass it on to the WorkflowInvoker to execute as normal:
Shared Sub Main() 'load a workflow ' in this case based on the current tick ' of the clock
' (( in this case 0 to 2 )) Dim pick As Integer = DateTime.Now.Second Mod 3 Dim filename As String =
String.Format("Workflow{0}.xaml", pick + 1) WorkflowInvoker.Invoke(ActivityXamlServices.Load(filename))
Console.WriteLine("Press ENTER to exit") Console.ReadLine()
End Sub
Code snippet from DynamicallyLoadingWorkflow
The preceding code expects three XAML files in the same directory as the executable It randomly selects one of the three, loads it using ActivityXamlServices.Load, and executes it You can run this multiple times to confirm that it selects the different workflows
While this is a simple example of loading the workflows dynamically, the method can be quite useful when building workflow applications For example, you may have separate workflows based on customer type or product You can use this method to load the correct workflow from a library of workflows as needed In addition, as your needs change, you can update the XAML files, without having to update your application
to reflect the changes
rehosTing The WorKfloW designer
One common request when working with workflows is to enable users to create and edit their own workflows
In the past, this has been problematic because you’d then have to either recreate the functionality yourself using WPF or figure out the interfaces required to get it to work With this version of WF, however, it has become much easier
You can host the workflow designer surface in any WPF application by creating a new instance of
the WorkflowDesigner class and inserting the View property into the location of the host The
WorkflowDesigner class also makes the standard property grid available to your application using
the PropertyInspectorView property
Create a new WPF application to host the workflow designer The main window of the application will host
a collection of available controls, as well as the workflow designer and property window The following code shows the XAML for the application:
Trang 14<sys:String x:Key=”AssemblyName”>System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</sys:String>
<tool:ToolboxItemWrapper.ToolName>
System.Activities.Statements.Sequence </tool:ToolboxItemWrapper.ToolName>
</tool:ToolboxItemWrapper>
<tool:ToolboxItemWrapper AssemblyName=”{StaticResource AssemblyName}”>
<tool:ToolboxItemWrapper.ToolName>
System.Activities.Statements.WriteLine </tool:ToolboxItemWrapper.ToolName>
</tool:ToolboxItemWrapper>
<tool:ToolboxItemWrapper AssemblyName=”{StaticResource CustomActivityAssembly}”>
<tool:ToolboxItemWrapper.ToolName>
CustomActivities.EncryptActivity </tool:ToolboxItemWrapper.ToolName>
</tool:ToolboxItemWrapper>
<tool:ToolboxItemWrapper AssemblyName=”{StaticResource CustomActivityAssembly}”>
<tool:ToolboxItemWrapper.ToolName>
CustomActivities.DecryptActivity </tool:ToolboxItemWrapper.ToolName>
<Border Name=”DesignerBorder” Grid.Column=”1” Grid.RowSpan=”2” />
<Border Grid.Row=”2” Grid.Column=”0” Name=”PropertyGridBorder” />
</Grid>
</Window>
The application’s main window uses a grid to lay out the ”Toolbox” of available activities added above and
a property view on the left, and the bulk of the window hosting the designer The designer and property grid will be added in code later, but Border controls have been added at the appropriate locations in the XAML where they will appear Figure 26-21 shows the resulting window in the designer
Notice that a custom namespace has been added to the XAML for the System.Activities.Presentation namespace This includes the classes that will be used to insert the Toolbox items You need to add each of the desired activities individually This gives you the flexibility to customize the controls you present to end users
rehosting the Workflow Designer ❘ 901
Trang 15In addition to the standard controls, you can also include custom controls I created a new Activity Library project (CustomActivities) and added the EncryptActivity and DecryptActivity activities to that project
I then referenced that project from this one If you look at the preceding XAML, you will see a new resource created pointing at that assembly The activities are then loaded just as you load the standard activities
All that remains is to create the new instance of the WorkflowDesigner, and insert it into the application:Imports System.Activities
Imports System.Activities.Core.Presentation Imports System.Activities.Presentation
Class MainWindow Public Sub New() InitializeComponent()
'load the standard control metadata (for the "toolbox") Dim designerMeta As New DesignerMetadata
designerMeta.Register()
'create the new design surface Dim designer As New WorkflowDesigner() 'adding a sequence as a default activity designer.Load(New System.Activities.Statements.Sequence())
'add the designer into the app DesignerBorder.Child = designer.View 'add the default property grid to the app PropertyGridBorder.Child = designer.PropertyInspectorView End Sub
Code snippet from RehostingDesigner
The DesignerMetadata class provides the information used by the designer to display the controls
on the design surface If you fail to register this class first, the designer won’t be able to draw the
appropriate designers for each control
figure 26-21
Trang 16You can customize the WorkflowDesigner before adding it to the application In this case, a default Sequence activity is added.
Finally, the designer and property window are inserted into the main window The final result (see Figure 26-22) allows the end user to create or edit workflows Saving the workflow is left as an exercise for you (but the WorkflowDesigner.Save and WorkflowDesigner.Load methods would likely come
in handy)
figure 26-22
summary
While Windows Workflow Foundation does not have the visual glitz of WPF or the broad reach of WCF, it is
a highly useful addition to the NET Framework Most business applications have some need for workflows, and having a standard means of creating a workflow ensures that the workflow is fully featured and accurately reflects business needs As WF is readily available with the NET Framework, you no longer need to create your own workflow capabilities for each application Moreover, WF is extensible, so you can take advantage
of it in your applications without being limited to the included features
As with the other components of the NET Framework, WF integrates well into other applications, including Windows Forms and ASP.NET applications It provides the means to extract the frequently complex workflow from those applications and to graphically design it This graphical representation can be used to communicate the process to business users, increasing the chance that the workflow is represented correctly Finally, as business needs change, it is a simple process to update the workflow, without requiring changes to the core application
summary ❘ 903
Trang 18localization
WhaT you Will learn in This chaPTer
Understanding culture types
➤ Getting culture settings from a thread
➤ Declaring culture in ASP NET
➤ Understanding diff erences in dates
➤ Understanding diff erences in currency & numbers
➤ Understanding diff erences in sorting
➤ Using culture specifi c resource fi les
➤
As the audience for an application expands, businesses often realize they need to globalize the application Of course, the ideal is to build the application to handle an international audience right from the start, but in most cases this may not be feasible because building for localized versions requires extra work and cost
The core of any localization effort is the translation of resources, and user interface changes Such changes are application specifi c and therefore not really open to generic implementation across the multitude of potential cultures for which you might choose to target an application However, some common elements of localization such as date support or numeric and currency formats can
be implemented by NET Framework classes
The NET Framework has made a considerable effort to support the internationalization of NET applications API support, server controls, and even Visual Studio itself equip you to do the extra work required to bring your application to an international audience This chapter looks at some of the important items to consider when building your applications for the world
culTures and regions
As an example, the ASP.NET page that is pulled up in an end user ’ s browser runs under a specifi c culture and region setting When building an ASP.NET application or page, the defi ned culture in which it runs is dependent upon a culture and region setting specifi ed either in the server in which the application is run or in a setting applied by the client (the end user) By default, ASP.NET runs under a culture setting defi ned by the server Stated simply, unless you specifi cally look for a client ’ s requested culture, your application will run based on the server ’ s culture settings
Trang 19The world is made up of a multitude of cultures, each of which has a language and a set of defined ways
in which it views and consumes numbers, uses currencies, sorts alphabetically, and so on The NET
Framework defines languages and regions using the Request for Comments 1766 standard definition (tags
for identification of languages — www.ietf.org/rfc/rfc1766.txt), which specifies a language and region using two-letter codes separated by a dash The following table provides examples of some culture definitions:
en-GB English language; United Kingdom (Great Britain)
The examples in this table define five distinct cultures These
five cultures have some similarities and some differences
Four of the cultures speak the same language (English), so
the language code of “en” is used in these culture settings
Following the language setting is the region setting Even
though most of these cultures speak the same language, it is
important to distinguish them further by setting their region
(such as US for the United States, GB for the United Kingdom,
AU for Australia, and CA for Canada) These settings reflect the
fact that the English used in the United States is slightly different
from the English used in the United Kingdom, and so forth
Beyond language, differences exist in how dates and numerical
values are represented This is why a culture’s language and
region are presented together
The differences between the cultures in the table do not
break down by region only Many countries contain more
than a single language, and each may have its own preference
for notation of dates and other items For example, en-CA
specifies English speakers in Canada Because Canada is not
only an English-speaking country, it also includes the culture
setting of fr-CA for French-speaking Canadians
understanding culture Types
The culture definition just given is called a specific culture
definition This definition is as detailed as you can possibly
get, defining both the language and the region The other
type of culture definition is a neutral culture definition Each
specific culture has a specified neutral culture with which it is
associated For instance, the English language cultures shown
in the previous table are separate, but they also belong to one
neutral culture: EN (English) The diagram presented in Figure
27-1 illustrates how these culture types relate to one another
From this diagram, you can see that many specific cultures belong to a neutral culture Higher in the
hierarchy than the neutral culture is an invariant culture, which is an agnostic culture setting that should be
utilized when passing items (such as dates and numbers) around a network When performing these kinds
of operations, you should make your back-end data flows devoid of user-specific culture settings Instead, apply these settings in the business and presentation layers of your applications
en-USen-GBen-AU
es-ESes-MXes-ARen-CA
InvariantCulture
EN(Neutral Culture)
ES(Neutral Culture)
figure 27-1
Trang 20In addition, pay attention to neutral culture when working with your applications In most cases, you are going to build applications with views that are more dependent on a neutral culture than on a specific culture For instance, if you have a Spanish version of your application, you’ll probably make this version available to all Spanish speakers regardless of where they live In many applications, it won’t matter whether the Spanish speaker is from Spain, Mexico, or Argentina In cases where it does make a difference, use the specific culture settings.
looking at your Thread
When the end user requests an ASP.NET page or runs a Windows Forms dialog, the item is executed on a thread from the thread pool That thread has a culture associated with it You can get information about the culture of the thread programmatically and then check for particular details about that culture
To see an example of working with a thread and reading the culture information of that thread, start with the basic Windows Forms application created in Chapter 1 To reproduce this create a new project called ProVB2010_Localization, and add the appropriate button and text box controls A copy of the code in this chapter is part of the code download with the name ProVB2010_Localization
Add a new Sub DisplayCultureInfo and have it called by the Click event handler for the test button
on the form When the TestButton_Click event is fired, the user’s culture information is retrieved and displayed in the TextBox control The code for the new Sub is presented here:
Private Sub DisplayCultureInfo() Dim ci As New System.Globalization.CultureInfo(
System.Threading.Thread.CurrentThread.CurrentCulture.ToString()) TextBox1.Text = "CURRENT CULTURE'S INFO" & Environment.NewLine TextBox1.Text += "Name: " & ci.Name & Environment.NewLine TextBox1.Text += "Parent Name: " & ci.Parent.Name & Environment.NewLine TextBox1.Text += "Display Name: " & ci.DisplayName & Environment.NewLine TextBox1.Text += "English Name: " & ci.EnglishName & Environment.NewLine TextBox1.Text += "Native Name: " & ci.NativeName & Environment.NewLine TextBox1.Text += "Three Letter ISO Name: " &
ci.ThreeLetterISOLanguageName & Environment.NewLine TextBox1.Text += "Calendar Type: " & ci.Calendar.ToString() & Environment.NewLine End Sub
Code snippet from Form1.vb
This simple form creates a CultureInfo object from the System.Globalization namespace and assigns the culture from the current thread that is running using the System.Threading Thread.CurrentThread.CurrentCulture ToString call Once the CultureInfo object is populated with the end user’s culture, details about that culture can be retrieved using a number of available properties that the CultureInfo object offers Example results of running the form are shown in Figure 27-2
Note that in the code download there is an additional button on the form based on additional changes that are made to this sample project
The CultureInfo object contains a number of properties that provide you with specific culture figure 27-2
Cultures and regions ❘ 907
Trang 21information The items displayed are only a small sampling of what is available from this object From this figure, you can see that the en-US culture is the default setting in which the thread executes In addition
to this, you can use the CultureInfo object to get at a lot of other descriptive information about the culture You can always change a thread’s culture on the overloads provided via a new instantiation of the CultureInfo object, as shown here:
Private Sub DisplayCultureInfo()
System.Threading.Thread.CurrentThread.CurrentCulture = New Globalization.CultureInfo(“th-TH”)
Dim ci As Globalization.CultureInfo = System.Threading.Thread.CurrentThread.CurrentCulture
' Dim ci As New System.Globalization.CultureInfo(
' System.Threading.Thread.CurrentThread.CurrentCulture.ToString()) TextBox1.Text = "CURRENT CULTURE'S INFO" & Environment.NewLine TextBox1.Text += "Name: " & ci.Name & Environment.NewLine TextBox1.Text += "Parent Name: " & ci.Parent.Name & Environment.NewLine TextBox1.Text += "Display Name: " & ci.DisplayName & Environment.NewLine TextBox1.Text += "English Name: " & ci.EnglishName & Environment.NewLine TextBox1.Text += "Native Name: " & ci.NativeName & Environment.NewLine TextBox1.Text += "Three Letter ISO Name: " &
ci.ThreeLetterISOLanguageName & Environment.NewLine TextBox1.Text += "Calendar Type: " & ci.Calendar.ToString() & Environment.NewLine End Sub
Code snippet from Form1.vb
In this example, only a couple of lines of code
are changed to assign a new instance of the
CultureInfo object to the CurrentCulture
property of the thread being executed by
the application The culture setting enables the
CultureInfo object to define the culture you
want to utilize In this case, the Thai language of
Thailand is assigned The results produced in the
TextBox control are illustrated in Figure 27-3
From this figure, you can see that the
.NET Framework provides the native name
of the language used even if it is not a Latin-based
letter style In this case, the results are presented
for the Thai language in Thailand, including some
of the properties associated with this culture (such
as an entirely different calendar than the one used
in Western Europe and the United States)
declaring culture globally in asP.neT
ASP.NET enables you to easily define the culture that is used either by your entire ASP.NET application or
by a specific page within your Web application, using what are termed server-side culture declarations You
can specify the culture for any of your ASP.NET applications by means of the appropriate configuration files To demonstrate this, close the ProVB2010_Localization application you started with and create a new ASP.NET website called ProVB_Russian Alternatively, you can open this download folder as a website in Visual Studio 2010 On the default.aspx page add a new Calendar control from the toolbox, following the text: Welcome to ASP.NET!
figure 27-3
Trang 22To change the default language used by this control you can specify culture settings in the web.config file
of the application itself, as illustrated here:
Code snippet from ProVB_Russian\web.config
Only the <globalization> line will need to be added to your default web.config file; it should also be noted that based on the page specific settings described below, this line has been commented out in the code download
Note the two attributes represented: culture and uiCulture The culture attribute enables you to define the culture to use for processing incoming requests, whereas the uiCulture attribute enables you to define the default culture needed to process any resource files in the application (use of these attributes is covered later
In the preceding snippet, the culture established for this ASP.NET application is the Russian language in the country of Russia In addition to setting the culture at either the server-wide or the application-wide level, another option is to set the culture at the page level, as shown here:
<%@ Page Title="Home Page" Language="VB" MasterPageFile="~/Site.Master"
AutoEventWireup="false"
CodeFile="Default.aspx.vb" Inherits="_Default"
UICulture="ru-RU" Culture="ru-RU"%>
%>
Code snippet from ProVB_Russian\default.aspx
This example specifies that the Russian language and culture settings are used for everything on the page You can see this
in action by using this @Page directive and a simple calendar control on the page Figure 27-4 shows the output Notice that marking the page as using Russian settings does not automatically translate text within the page; it only updates the embedded control added to the page
adopting culture settings in asP.neT
In addition to using server-side settings to define the culture for your ASP.NET pages, you also have the option to define the culture according to what the client has set as his or her preference in a browser instance
When end users install Microsoft’s Internet Explorer or some other browser, they have the option to select their preferred cultures in a particular order (if they have selected more than a single culture preference) To see this in action in IE, select Tools ➪ Internet Options
figure 27-4
Cultures and regions ❘ 909
Trang 23from the IE menu On the first tab provided (General) is
a Languages button at the bottom of the dialog Select
this button and you are provided with the Language
Preference dialog shown in Figure 27-5
To add any additional cultures to the list, click the Add
button and select the appropriate culture from the
list After you have selected any cultures present in
the list, you can select the order in which you prefer to
use them Thus, a user with multiple settings in this list
will have a version of the application with their first
language choice before anything else; if a version that
supports that language is not available, their second and
then consecutive versions are checked The first available
language matching one of their preferences will be
presented
Making language selections, the end user can leverage
the automatic culture recognition feature provided in
ASP.NET Instead of specifying a distinct culture in any
of the configuration files or from the @Page directive, you
can also state that ASP.NET should automatically select
the culture provided by the end user requesting the page
This is done using the auto keyword, as illustrated here:
<%@ Page UICulture="auto" Culture="auto" %>
With this construction in your page, the dates, calendars,
and numbers appear in the preferred culture of the
requester What happens if you have translated resources
in resource files (shown later in the chapter) that depend on a culture specification? Or what if you have only specific translations and therefore can’t handle every possible culture that might be returned to your ASP.NET page? In this case, you can specify the auto option with an additional fallback option if ASP NET cannot find any of the culture settings of the user (such as culture-specific resource files) This usage is illustrated in the following code:
<%@ Page UICulture="auto:en-US" Culture="auto:en-US" %>
In this case, the automatic detection is utilized; but if the culture preferred by the end user is not present, then en-US is used
TranslaTing Values and BehaViors
In the process of globalizing your NET application, you may notice a number of aspects that are handled differently compared to building an application that is devoid of globalization, including how dates are represented and how currencies are shown This section looks at some of these issues
understanding differences in dates
Different cultures specify dates and time very differently For instance, take the following date as an example:08/11/2008
Is this date August 11, 2008 or is it November 8, 2008? It should be the job of the business logic layer
or the presentation layer to convert all date and times for use by the end user To avoid interpretation errors, always use the same culture (or invariant culture) when storing values, such as dates and times, in a database or other data store
figure 27-5
Trang 24Setting the culture at the server level in ASP.NET or within a Windows Forms application, as shown in the earlier examples, enables your NET application to make these conversions for you You can also simply assign a new culture to the thread in which the code is running For instance, consider the following sub, which can be called from the ButtonTest Click event handler (note that this Sub is dependent on these Imports statements):
Imports System.Globalization Imports System.Threading
Private Sub DisplayCalendarByCulture() Dim dt As DateTime = New DateTime(2010, 3, 2, 13, 5, 1, 10)
Thread.CurrentThread.CurrentCulture = New CultureInfo("pt-br") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _ dt.ToString() & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _ dt.ToString() & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("es-mx") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _ dt.ToString() & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("es-es") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _ dt.ToString() & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("ru-RU") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _ dt.ToString() & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("fi-FI") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _ dt.ToString() & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("ar-SA") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _ dt.ToString() & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("am-ET") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _ dt.ToString() & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("as-IN") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _ dt.ToString() & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("th-TH") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _
Translating Values and Behaviors ❘ 911
Trang 25dt.ToString() & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("zh-cn") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _ dt.ToString() & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("zh-tw") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _ dt.ToString() & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("ko-kr") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _ dt.ToString() & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("zh-hk") TextBox1.Text +=
Thread.CurrentThread.CurrentCulture.EnglishName & " : " & _ dt.ToString() & Environment.NewLine
End Sub
Code snippet from Form1.vb
Using the ProVB2010_Localization test form again, you can test this code The code snippet captures the current date time for output, but does so while referencing a dozen or more different cultures, one for each copy output to the screen The date/time construction used by the defined culture is written to the TextBox control The result from this code operation is presented in Figure 27-6
figure 27-6
Clearly, the formats used to represent a date/time value can be dramatically different between cultures — some, such as Saudi Arabia (ar-SA) and Thailand, (th-TH) use entirely different calendar baselines
Trang 26differences in numbers and currencies
In addition to date/time values, numbers are displayed quite differently from one culture to the next How can a number be represented differently in different cultures? Well, it has less to do with the actual number (although certain cultures use different number symbols) and more to do with how the number separators are used for decimals or for showing amounts such as thousands, millions, and more For instance, in the English culture of the United States (en-US), numbers are represented in the following fashion:
5,123,456.00From this example, you can see that the en-US culture uses a comma as a separator for thousands and a period for signifying the start of any decimals that might appear after the number is presented It is quite different when working with other cultures The following code block shows an example of representing numbers in other cultures:
Private Sub Numbers() Dim myNumber As Double = 5123456.0
Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US") TextBox1.Text += Thread.CurrentThread.CurrentCulture.EnglishName &
" : " & myNumber.ToString("n") & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("vi-VN") TextBox1.Text += Thread.CurrentThread.CurrentCulture.EnglishName &
" : " & myNumber.ToString("n") & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("fi-FI") TextBox1.Text += Thread.CurrentThread.CurrentCulture.EnglishName &
" : " & myNumber.ToString("n") & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("fr-CH") TextBox1.Text += Thread.CurrentThread.CurrentCulture.EnglishName &
" : " & myNumber.ToString("n") & Environment.NewLine
End Sub
Code snippet from Form1.vb
Adding this code to your project and running it from the click event produces the results shown in Figure 27-7
As you can see, cultures show numbers in numerous different formats The second culture listed in the figure, vi-VN (Vietnamese in Vietnam), constructs
a number exactly the opposite from the way it is constructed in en-US The Vietnamese culture uses periods for the thousand separators and a comma for signifying decimals, a somewhat common format around the world Finnish uses spaces for the thousand separators and a comma for the decimal separator, whereas the French-speaking Swiss use an apostrophe for separating thousands, and a period for the decimal separator This demonstrates that not only do you need to consider dates and language constructs, but that it is also important to “translate” numbers to the proper format so that users of your application can properly understand the numbers represented
figure 27-7
Translating Values and Behaviors ❘ 913
Trang 27Another scenario in which you represent numbers is when working with currencies It is one thing to
convert currencies so that end users understand the proper value of an item; it is another to translate the
construction of the currency just as you would a basic number
Each culture has a distinct currency symbol used to signify that a number represented is an actual currency value For instance, the en-US culture represents currency in the following format:
$5,123,456.00The en-US culture uses a U.S dollar symbol ($), and the location of this symbol is just as important as the symbol itself For en-US, the $ symbol directly precedes the currency value (with no space in between the symbol and the first character of the number) Other cultures use different symbols to represent currency and often place those currency symbols in different locations
Create another Sub that can be called from the button’s click event handler, and this time format the same numbers using the built-in NET currency formatting, as shown in the following code:
Private Sub Currency() Dim myNumber As Double = 5123456.0
Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US") TextBox1.Text += Thread.CurrentThread.CurrentCulture.EnglishName &
" : " & myNumber.ToString("c") & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("vi-VN") TextBox1.Text += Thread.CurrentThread.CurrentCulture.EnglishName &
" : " & myNumber.ToString("c") & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("fi-FI") TextBox1.Text += Thread.CurrentThread.CurrentCulture.EnglishName &
" : " & myNumber.ToString("c") & Environment.NewLine
Thread.CurrentThread.CurrentCulture = New CultureInfo("fr-CH") TextBox1.Text += Thread.CurrentThread.CurrentCulture.EnglishName &
" : " & myNumber.ToString("c") & Environment.NewLine End Sub
Code snippet from Form1.vb
Executing the preceding Sub displays the output
shown in Figure 27-8
Not only are the numbers constructed quite
differently from one another, but the currency
symbol and the location of the symbol in regard to
the number are quite different as well
Note that when you are using currencies on an
ASP.NET page and you have provided an automatic
culture setting for the page as a whole (such as
setting the culture in the @Page directive), you need
to specify a specific culture for the currency that is
the same in all cases Unlike dates, for which the
differences are primarily display oriented, with a
currency there is an expectation of value conversion
Thus, reformatting a currency can cause expensive
errors unless you are actually doing a currency
conversion
For instance, if you are specifying a U.S dollar currency value in your data, , you do not want your ASP NET page to display that value as something else (for example, the euro) based on translating the remainder
figure 27-8
Trang 28of the page information to another language Of course, if you actually performed a currency conversion and showed the appropriate euro value along with the culture specification of the currency, that makes sense and is the best solution.
Therefore, if you are using an automatic culture setting on your ASP.NET page and you are not converting
the currency, you should perform something similar to the following code for currency values:
Dim myNumber As Double = 5123456.00 Dim usCurr As CultureInfo = New CultureInfo("en-US") Response.Write(myNumber.ToString("c", usCurr))
understanding differences in sorting
You have learned to translate textual values and alter the construction of the numbers, date/time values, currencies, and more when you are globalizing an application You should also take care when applying culture settings to some of the programmatic behaviors that you establish for values in your applications One operation that can change based upon the culture setting applied is how NET sorts strings You might think that all cultures sort strings in the same way (and generally they do), but sometimes differences exist For example, the following shows a sorting operation occurring in the en-US culture:
Imports System.Collections.Generic
Private Sub Sorting() Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US") 'Thread.CurrentThread.CurrentCulture = New CultureInfo("fi-FI")
Dim myList As List(Of String) = New List(Of String)
myList.Add("Washington D.C.") myList.Add("Helsinki") myList.Add("Moscow") myList.Add("Warsaw") myList.Add("Vienna") myList.Add("Tokyo")
myList.Sort()
For Each item As String In myList TextBox1.Text += item.ToString() & Environment.NewLine Next
End Sub
Code snippet from Form1.vb
For this example to work, you have to reference the System.Collections and the System.Collections Generic namespaces because this example makes use of the List(Of String) object
In this example, a generic list of capitals from various countries of the world is created in random order Then the Sort method of the generic List(Of String) object is invoked This sorting operation sorts the strings according to how sorting is done for the defined culture in which the application thread is running The preceding code shows the sorting as it is done for the en-US culture The result of this operation when used within the ProVB2010_Localization form is shown in Figure 27-9
This is pretty much what you would expect Now, however, change the previous example so that the culture
is set to the Finnish culture Do this by uncommenting the second line of the Sub Sorting and commenting out the first line of the Sub Sorting which sets the “en-US” culture settings, in the preceding snippet
If you run the same bit of code under the Finnish culture setting, you get the results presented in Figure 27-10
Translating Values and Behaviors ❘ 915
Trang 29asP.neT resource files
When you work with ASP.NET, resources are handled by resource files A resource file is an XML-based file that has a resx extension You can have Visual Studio help you construct this file Resource files provide a set of items that are utilized by a specified culture In your ASP.NET applications, you store resource files as
either local resources or global resources The following sections describe how to use each type of resource.
making use of local resources
You might be surprised how easily you can build an ASP.NET page so that it can be localized into other
languages In fact, the only thing you need to do is build the ASP.NET page as you normally would and then
Trang 30asP.neT resource files ❘ 917
use some built-in capabilities from Visual Studio to convert the page to a format that enables you to plug in other languages easily
To see this in action, build a simple ASP.NET website called ProVB_Localization and open the Default aspx page as presented here Note that we have added a few simple controls to replace the default labels generated with a new page This page will be referred to later in the chapter as the “ASP.NET page code block.” Keep in mind that the downloaded code will not match this initial code snippet as this chapter modifies this code to support multiple languages
<%@ Page Title="Home Page" Language="VB" MasterPageFile="~/Site.Master" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %>
<asp:Label ID="Label1" runat="server"
Text="What is your name?"></asp:Label><br />
<br />
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="Submit Name" /><br />
Code snippet from ProVB_Localization\Default.aspx
As you can see, there is not much to this page It is composed of a couple of Label controls, as well as TextBox and Button controls Update the click event handler for Button1 to set the Label2.Text property text to the TextBox1.Text property value This way, when users enter their name into the text box, the Label2 server control is populated with the inputted name
The next step is what makes Visual Studio so great To change the construction of this page so that it can be localized easily from resource files, open the page in Visual Studio and ensure that you are in Design view
Next, using the Visual Studio menu, select Tools ➪ Generate Local Resource
Note that you can select this tool only when you are in the Design view of your page
Selecting Generate Local Resource from the Tools menu causes Visual Studio
to create an App_LocalResources folder in your project if you don’t have one already A resx file based upon this ASP.NET page is then placed in the folder For instance, if you are working with the Default.aspx page, then the resource file is named Default.aspx.resx (see Figure 27-11)
Right-click on the resx file, select View Code If View Code isn’t present
on your default menu, select Open With; you’ll get a dialog with a list of editor options From the Open With dialog, select the XML (Text) Editor as the program to open this file using the OK button After doing this, you should find the View Code option on the context menu for this file When the resx file opens, you’ll notice that the resx file is nothing more than an XML file with an associated schema at the beginning of the document The resource file that is generated for you takes every possible property of every translatable control on the page and gives each item a key value that can be referenced in your ASP.NET page Looking at the page’s code, note that all the text values you placed in the page have been retained, but
figure 27-11
Trang 31they have also been placed inside the resource file Visual Studio changed the code of the Default.aspx page as shown in the following code block:
<%@ Page Title="Home Page" Language="VB" MasterPageFile="~/Site.Master"
AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default"
culture="auto" meta:resourcekey="PageResource1" uiculture="auto" %>
<asp:Label ID="Label1" runat="server"
Text="What is your name?" meta:resourcekey="Label1Resource1"></asp:Label>
Code snippet from ProVB_Localization\Default.aspx
From this bit of code, you can see that the Culture and UICulture attributes have been added to the
@Page directive with a value of auto, thus, enabling this application to be localized In addition, the attribute meta:resourcekey has been added to each of the controls, along with an associated value This
is the key from the resx file that was created on your behalf Double-clicking on the Default.aspx.resx file opens the resource file in the Resource Editor, shown in Figure 27-12, built into Visual Studio Keep in mind the code download will have additional settings not shown if you are working along with the chapter
figure 27-12
Trang 32asP.neT resource files ❘ 919
Note that a few properties from each of the server controls have been defined in the resource file For instance, the Button server control has its Text and ToolTip properties exposed in this resource file, and the Visual Studio localization tool has pulled the default Text property value from the control based on what you placed there Looking more closely at the Button server control constructions in this file, you can see that both the Text and ToolTip properties have a defining Button1Resource1 value preceding the property name This is the key that is used in the Button server control shown earlier
In the following aspx source, a meta:resourcekey attribute has been added to a button control In this case it references Button1Resource1 All the properties using this key in the resource file (for example, the Text and ToolTip properties) are applied to this Button server control at runtime
<asp:Button ID="Button1" runat="server" Text="Submit Name"
meta:resourcekey="Button1Resource1" />
Code snippet from ProVB_Localization\Default.aspx
adding another language resource file
The Default.aspx.resx file created in the last section is used by the application as the default or invariant culture No specific culture is assigned to this resource file If for a given request no culture can be
determined, then this is the resource file that is utilized To add another resource file for the Default.aspx page that handles another language altogether, copy and paste the Default.aspx.resx file into the same App_LocalResources folder and rename the newly copied file If you use Default.aspx.fi-FI.resx, give the following keys the values shown to make a Finnish-language resource file:
Button1Resource1.Text Lähetä Nimi Label1Resource1.Text Mikä sinun nimi on?
PageResource1.Title NäytesivuOnce you have created this file, take an additional step and create a custom resource in both resource files using the key Label2Answer The Default.aspx.resx file should have the following new key:
Label2Answer HelloNow you can add the key Label2Answer to the Default.aspx.fi-FI.resx file as shown here:
Label2Answer HeiYou now have resources for specific controls, and a resource that you can access later programmatically
finalizing the Building of the Default.aspx Page
Finalizing the Default.aspx page, you want to add a Button1_Click event so that when the end user enters a name into the text box and clicks the Submit button, the Label2 server control provides a greeting pulled from the local resource files When all is said and done, your default page should have a code-behind element that matches the following code:
Partial Class _Default Inherits System.Web.UI.Page
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click Label2.Text = GetLocalResourceObject("Label2Answer") &
" " & TextBox1.Text End Sub
End Class
Code snippet from ProVB_Localization\Default.aspx.vb
In addition to pulling local resources using the meta:resourcekey attribute in the server controls on the page to access the exposed attributes, you can also access any property value contained in the local resource
Trang 33file by using the GetLocalResourceObject When using GetLocalResourceObject, you simply use the name of the key as a parameter, as shown here:
GetLocalResourceObject("Label2Answer")With the code from the Default.aspx page in place and the resource files completed, you can run the page, entering a name in the text box and then clicking the Submit Name button to get a response, as shown in Figure 27-13
What happened behind the scenes that caused this page to be constructed in this manner? First, only two resource files — Default.aspx.resx and Default.aspx.fi-FI.resx — are available The Default aspx.resx resource file is the invariant culture resource file, whereas the Default.aspx.fi-FI.resx resource file is for a specific culture (fi-FI) Because the browser requesting the Default.aspx page was set to en-US as the preferred culture, ASP.NET found the local resources for the Default.aspx page From there, ASP.NET checked for an en-US-specific version of the Default.aspx page Because there isn’t a specific page for the en-US culture, ASP.NET checked for an EN-(neutral culture)-specific page Not finding a page for the
EN neutral culture, ASP.NET was then forced to use the invariant culture resource file of Default.aspx resx, producing the page shown in Figure 27-13
If you now set your IE language preference as fi-FI and rerun the Default.aspx page, you’ll see a Finnish version of the page, as shown in Figure 27-14
figure 27-13
figure 27-14
Trang 34asP.neT resource files ❘ 921
In this case, having set the IE language preference to fi-FI, you are presented with this culture’s page instead
of the invariant culture page presented earlier ASP.NET found this specific culture through use of the Default.aspx.fi-FI.resx resource file
You can see that all the control properties that were translated and placed within the resource file are utilized automatically by ASP.NET, including the page title presented in the title bar of IE
neutral Cultures are Generally Preferred
When you are working with the resource files from this example, note that one of the resources is for a
specific culture The Default.aspx.fi-FI.resx file is for a specific culture — the Finnish language as spoken in Finland Another option would be to make this file work not for a specific culture, but instead for
a neutral culture To do so, simply name the file Default.aspx.FI.resx In this case, it doesn’t make any difference because no other countries speak Finnish; but it would make sense for languages such as German, Spanish, or French, which are spoken in multiple countries
For instance, if you are going to have a Spanish version of the Default.aspx page, you could definitely build it for a specific culture, such as Default.aspx.es-MX.resx This construction is for the Spanish language as spoken in Mexico With this in place, if someone requests the Default.aspx page with the language setting of es-MX, that user is provided with the contents of this resource file If the requester has a setting of es-ES, he or she will not get the Default.aspx.es-MX.resx resource file, but the invariant culture resource file of Default.aspx.resx If you are going to make only a single translation for your site
or any of your pages, construct the resource files to be for neutral cultures, not specific cultures
If you have the resource file Default.aspx.ES.resx, then it won’t matter if the end user’s preferred setting is set to es-MX, es-ES, or even es-AR — that user gets the appropriate ES neutral-culture version of the page
global resources
Besides using only local resources that specifically deal with a particular page in your ASP.NET application,
you also have the option to create global resources that can be used across multiple pages To create a
resource file that can be utilized across the entire application, right-click on the solution in the Solution Explorer of Visual Studio and select Add New Item From the Add New Item dialog, select Resource File Visual Studio prompts you to place this file in a new folder called App_GlobalResources You’ll see that this file already exists in the sample code download Once again, your first resource file is the invariant culture resource file Add a single string resource with the key LabelText and assign a long string value to this key The string “Non-Variant Format Label Text” was used in the code download Next, add a third Label control, Label3 to the bottom of your existing page
Now that you have the invariant culture resource file completed, the next step is to add another resource file, but this time name it Resource.es.resx Again, for this resource file, use a string key of LabelText and paste in the Spanish translation of the preceding text
The point of a global resource file is to have access to these resources across the entire application You can access the values that you place in these files in several ways One way is to work the value directly into any
of your server control declarations For instance, you can place the following privacy statement in a Label server control as shown here:
<asp:Label ID="Label3" runat="server"
Text='<%$ Resources: Resource, LabelText %>'></asp:Label>
Code snippet from ProVB_Localization\Default.aspx.vb
Trang 35With this construction in place, you can now grab the appropriate value of the LabelText global resource, depending on the language preference of the end user requesting the page To make this work, you use the keyword Resources followed by a colon Next, you specify the name of the resource file In this case, the name of the resource file is Resource, because this statement goes to the Resource.resx and Resource.es.resx files in order to find what it needs After specifying the particular resource file to use, the next item in the statement is the key — in this case, LabelText.
Another way to achieve the same result is to use some built-in dialogs within Visual Studio Highlight the server control you want in Visual Studio from Design view so that the control appears within the Properties window For my example, I highlighted a Label server control From the Properties window, click the button within the Expressions property This launches the Expressions dialog, where you can bind the LabelText value to the Text property of the control, as shown in Figure 27-15
Note that the resources provided via global resources are available in a strongly typed manner For instance, you can programmatically get at a global resource value by using the construction presented in the following example:
Label3.Text = Resources.Resource.LabelText.ToString()Figure 27-16 shows that you have full IntelliSense for these resource values
However in the case of the sample download the changes shown for the aspx file were maintained
(although commented out) Enabling this line in the sample application, combined with a request that specifies a Spanish language culture, results in a page with the Spanish text for Label3
Trang 36figure 27-16
resource files in WindoWs forms
Just as with ASP.NET, you can also work with resource files (.resx) for Windows applications using Visual Studio To see how to localize a Windows Forms application, you will want to reopen the ProVB_2010Localization project introduced earlier in this chapter In this case you are going to add a new form to your Localization project called UsingResx.vb
Like the ASP.NET form described earlier in this chapter (and identified as “ASP.NET page code block”), this Windows Forms dialog should contain a couple of Label controls, a Button, and a TextBox control Initially, your form (with its controls) should look like the one shown in Figure 27-17
resource files in Windows forms ❘ 923
figure 27-17
Trang 37Before you get started, turn on localization features for the form Keep in mind that the following steps can also be used for a form that already exists if you are converting an existing form to deal with more than one language.
Selecting the form in the designer, go to the Properties window and change the Localizable property to True This enables you to apply more than one language to a form and have the elements for a particular culture stored in a resource file
After you have set the Localizable property to True, you can then provide alternate language values for the controls on the form The properties that you currently have assigned to the controls are for the Default language setting
As with ASP.NET, if a culture is undetermined or a resource file is not available for this culture, then the Default settings are utilized To create a new language-specific set of resources, the first step is to change the Language property of the form to the desired language You will find this setting within the Language property of the form, as shown in Figure 27-18
Notice that the property window lists not only the Finnish language as an option, but also culture-specific options such as Finnish (Finland) As with ASP.NET, selecting the language can speed the process of creating localized version(s) of your application, as opposed to creating country-specific resources From the language property window shown in Figure 27-18, select Finnish as the language and then change the values of the three controls as follows:
Button1.Text Lähetä Nimi Label1.Text Mikä sinun nimi on?
Label2.Text Hei
figure 27-18
Trang 38Note that once you change the value of the Language property, the title of the design window in Visual Studio is also updated to reflect that you are looking at the design for the Finnish view.
Next you are going to set up a couple of methods for your form First, by double-clicking on the form’s button, you will create a Button1_Click event Within this event you’ll add code to assign the value of the TextBox1.Text property to Label2 Additionally, you will add a constructor to the form so that you can specify the current culture info for the thread on which the form is being created The code-behind for this form is as follows:
Imports System.Threading Imports System.Globalization
Public Class UsingResx Sub New()
'Thread.CurrentThread.CurrentCulture = New CultureInfo("fi-FI") 'Thread.CurrentThread.CurrentUICulture = New CultureInfo("fi-FI")
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Label2.Text += TextBox1.Text
End Sub End Class
Code snippet from UsingResx.vb
The preceding code shows that you have added two lines that will specify the culture info on the current thread prior to processing the form Leaving these two lines commented out, running the application, and using the Open UsingResx button to open the UsingResx form produces the output shown in Figure 27-19 After doing this, uncomment the two lines, which will simulate a user whose system settings are defined
as Finnish Again, accessing the UsingResx form, the display automatically changes to the one shown in Figure 27-20
resource files in Windows forms ❘ 925
Trang 39Where are all the translations stored? Just as with ASP.NET, they are stored in
the resource file for this form Using the Solution Explorer to show all the files
in your solution, you will now find a UsingResx.resx file and a UsingResx
.fi.resx file, as shown in Figure 27-21
Opening the UsingResx.resx file will cause Visual Studio to open the file in a
manner that enables you to directly edit the values it stores The default resource
file stores some type references as well as other properties of the controls on the
form, as shown in Figure 27-22
Opening the UsingResx.fi.resx file instead shows only the three changed
properties and the updated page title The rest of the properties are read from
the default resource file The contents of the Finnish resource file are presented
in Figure 27-23
figure 27-21
figure 27-22
figure 27-23
Trang 40Visual Studio 2010 provides an editor for working with resource files You have already seen some of the views available from the Resource Editor Resources are categorized visually according to the data type
of the resource This chapter has covered only the handling of strings, but other categories exist (such as images, icons, audio files, miscellaneous files, and other items) These options are shown in Figure 27-24
figure 27-24
summary
This chapter has looked at some of the localization tools available to you It started with a review of the Culture types and how to determine the preferred culture for either a thread or Web request It looked at understanding how different cultures may treat the same date or number differently for display and just
as importantly how NET can automate this handling for you It also examined differences with currency with a warning about the need to convert a value and not just swap the display formatting when dealing with currency The chapter then looked at how NET supports both Windows Forms and ASP.NET use of multiple resource files to provide support for different languages and cultures
While NET has provided many tools to help you with this process, you should keep in mind that these tools only make the process easier when looking; when you want to localize an application, you need to plan to work with someone familiar with the language, and ideally the culture, you will target
In addition to making many of the changes described in this chapter, localization is a process that requires consideration of multiple different time zones, consideration for information that reads Left to Right vs cultures which expect information to flow Right to Left, and other issues that are outside the scope of the tools you use While that seems like a lot, you’ll note that the same concepts for using resource files exist between Windows Forms and ASP.NET
summary ❘ 927