1. Trang chủ
  2. » Công Nghệ Thông Tin

Professional Visual Basic 2010 and .neT 4 phần 8 docx

133 417 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Windows Workflow Foundation
Thể loại Giáo trình
Định dạng
Số trang 133
Dung lượng 3,85 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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 3

Public 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 4

End 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 5

Build 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 6

figure 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 7

Finally, 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 8

VariaBle 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 9

Building 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 10

Dim 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 11

Dim 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 12

You 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 13

the 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 15

In 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 16

You 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 18

localization

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 19

The 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 20

In 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 21

information 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 22

To 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 23

from 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 24

Setting 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 25

dt.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 26

differences 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 27

Another 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 28

of 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 29

asP.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 30

asP.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>&nbsp;

<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 31

they 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 32

asP.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 33

file 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 34

asP.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 35

With 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 36

figure 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 37

Before 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 38

Note 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 39

Where 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 40

Visual 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

Ngày đăng: 12/08/2014, 23:23

TỪ KHÓA LIÊN QUAN