ARTICLE

Developer Roadmap For Building Smart Client Applications

Posted by Jay Tallamraju Articles | Mobile & Embedded August 08, 2006
This article is a developer Roadmap For Building Smart Client Applications.
Reader Level:

Executive Summary

Microsoft released many different tools and technologies recently and still more to come. For some people all this is happening at a time, where they are just becoming comfortable with .NET 1.1 and Visual Studio .NET 2003. Also there are few developers who are just getting a hold on application blocks or Enterprise Library vs. custom coding. Unfortunately not everyone get the time to update their skills, using new technology, mostly due to current workload and projects that are already developed in .NET 1.1. There may be few developers overwhelmed and confused of all this stuff dumped on them and not sure where to start. This article focuses on clearing some confusion in this area and helping everyone to get a high-level view of most of the new technology introduced recently.

Focus of this article is on concepts and high-level view of new tools/technologies. Because it is difficult to cover breadth and depth of technology at the same time, I try to avoid few details and will provide links to required documentation or resources. It is also important not to repeat the information that is already well published in many books and articles. So in this article I try to guide everyone across different technologies/tools by providing more solid learning plan with useful tips and references in each section.

This article covers tools and technologies that are mainly useful for Windows Forms Smart Client development. It is not in scope to cover details of web and mobile development in this article but few concepts in this article [Enterprise library, Data access] may be applicable to some of them also. So when I refer 'everything', I am only referring to technologies under "Windows Forms Smart Client" development. They are still few drops in the ocean of technology.

Audience

.NET 1.1 Developers: I have covered technologies in orderly fashion to help readers with only core .NET 1.1 framework and Visual Studio .NET 2003 skills. So this should give the reader, comfort over new technology without getting overwhelmed by it. I also think this approach might be useful for greater audience. Please see following map to figure if you would like to skip few sections.

Managers: Software management is no longer non-technical and it is important to get up to date with new technology, even if you are managing a software project. Right level of exposure to technology helps managers to take right decisions at right time. This article might help managers to get a hold of what is going on around Smart Client development.

Developing Windows Forms Smart Clients

Technology/Skill map

Please see the technology/skill map below to decide what STEPs you want to read in this article. You are free to read the whole article or go by the map below. I preferred to call them STEPS  than topics as I want stress the importance of order.

SCA1.gif

Overview: Old days Microsoft used to deliver operating system and IDE tools with few SDKs. For developing enterprise-ready applications developers used to rely on few 3rd part controls/tools. Recent work that Microsoft Patterns and Practices group is doing contributing significantly to boost enterprise development. Libraries and tools delivered by this group are also helping to bring consistency in design/development approaches across many projects.

STEP 1: .NET 2.0 Framework

1.1: CLR/CTS and framework

.NET Framework is foundation for all development in .NET. My intention is not to repeat text from already published articles here. But I included this step because getting hold of framework concepts and new features in .NET 2.0 is important foundation, to understand other technologies.

Terminology update for newcomers:

Name/Abbreviation Description
CLR Common Language Runtime - Runtime environment provided by .NET Framework
CTS Common Type System - Support for adding types that can be available in multiple languages
Assembly Dlls in Managed world
Application Domain Process in managed world
Managed/Unmanaged Managed-code refers to anything that is managed by .NET Framework. Unmanaged-code refers to code that uses operating system features/api/other unmanaged libraries directly
GAC Global Assembly Cache

1.2. Generics

Generics are very useful and allow us to write type agnostic code. If you have used STL (Standard Template Library), generics follow similar concepts. In .NET 1.1 days, to create type-safe collection, we have to create new collection class for each type of collection. This creates duplicate/repeated code and only thing normally changes, is the type that collection holds. With .NET Generics we can just create one class with type as variable and write the code that uses the type variable.

Looking at following code might give better picture:

.NET 1.1 code:

public Employee GetValue(Employee val1, Employee val2)

{

          return (val1 == null ? val2 : val1);

}

 

public string GetValue(string val1, string val2)

{

          return (val1 == null ? val2 : val1);

}

.NET 2.0 code using Generics:

public T GetValue<T>(T val1, T val2)

{

          return (val1  == null ? val2 : val1);

}

 

Example Usage: 

String val = GetValue<string>("val1", "val12");

Employee emp = GetValue<Employee>(emp1, emp2)

NOTE: .NET 2.0 introduces nullable types (T? or System.Nullable<T>) and new operator '??'. It simplifies code when assignig nullable value to non-nullable value [Example: "val3 = val1 ?? val2;" - will assign val1 to val3 if val1 is not null, otherwise val2]. 

1.3. Partial Classes:

Partial classes are very useful if we understand where to use them and how to use them. They allow clear separation of designer generated code and application specific code in to separate physical files, giving more flexibility in extending functionality. For example, in .NET 1.1, code is generated by VS.NET 2003, for typed DataSet and there is no direct way to override generated code. This is because VS.NET 2003 overrides developer changes to the code, when typed DataSet is regenerated. VS.NET 2005 uses partial classes for this approach allowing us to implement few additional custom interfaces OR properties/methods that might be useful in many scenarios.

Another example: There may be other scenarios where we need to implement multiple interfaces but would like to do it in same class. Three developers can create three class files each implementing different interfaces and another developer might implement same class derived from a base class. When compiled, generated class will be derived from single base class and implements all interfaces. Once the class is compiled, from developer point of view, a partial class is as good as regular class.

VS.NET 2005 uses partial classes to add any designer generated code for controls/forms. This approach separates application code and generated code and makes it easy to maintain.

1.4. ClickOnce:

ClickOnce supports easy deployment of Windows Forms application and if used with VS .NET 2005 'Publish' feature, it is really 'Click Once' to deploy the application. ClickOnce precedes "No touch deployment" and there is no need for developers to directly use Updater Application Block.

ClickOnce is new deployment technology in .NET 2.0 that makes deploying Windows Forms applications a breeze. With bootstrap support and using per user installation features, it brings web deployment comfort to Windows Forms applications. You can deploy a smart client or regular thick-client windows based application using ClickOnce technology.

See details in STEP 2, for rich support in VS.NET 2005 to add bootstrap support and additional dependent dlls to the project, using Click Once deployment. 

1.5. ADO.NET 2.0

Few important changes that might be useful to know in ADO.NET are following:

  • Multiple Active Results Sets (MARS): You can now keep multiple Data Readers open on same connection. While I think this feature is useful in few scenarios, it is better to use it where it makes sense. Also I am not clear if all ADO.NET providers support this feature. Microsoft SQL Server and Oracle OPD.NET ADO.NET providers support this feature and you may not care for the rest.
  • DataTable: DataTable now has many useful methods that were available only at DataSet level. So you no longer need to create DataSet and attach your DataTable to make use of these methods and call them directly on DataTable.
  • DataSet:  While there are few other enhancements in this area, one improvement is that DataSet now supports binary serialization, which might improve performance [mainly for .NET Remoting applications]

You can see many new features in few links under "Developer Reference" section below. You need to be aware that not all features may be available/supported in all ADO.NET providers. Some may be SQL Server specific and are supported only on Microsoft SQL Server ADO.NET provider.

If you are writing data access code, I recommend reading the article "Building Data Access Layer with Visual Studio 2005". There are many areas in data access that cannot be covered in this article but I can see people driving towards more ORM tools or code-generated approach OR object oriented approach. (See Microsoft's ADO.NET Entity Framework). While you can easily get lost in different methodologies and approaches, data access layer is very important for any project. So choose the one best suit your needs and not everything may fit every size of the project.

My recommendation is to stick with Enterprise Library Data Access Application block and write database agnostic code. Use ADO.NET interface types and avoid provider specific types as much as possible. If you need to write a lot of Data Access code you may want to take the time to evaluate best option from above.

1.6. Windows Forms 2.0

Good news to user interface developers is that it is becoming more and more easier; to create more professional looking windows forms application. Of course, good looking user interface is not enough to sell a product, usability, user responsiveness, performance and stability are also important. New features/controls supported in Windows Forms will help you address these concerns better. Following are few key changes I think are more useful. Please see reference section for more details.

  • Application/User scope settings: There is good support for application-scope and user-scope settings. User scope settings are saved by default to user's local settings folder. This is true mainly for click once applications.
  • Background worker component: It is so common in user interface applications, to do the time consuming work on background thread. Background Worker component has good support for doing background work on separate thread. It is very easy to use and eliminates writing custom code, creating threads directly to implement background processing.
  • SplitContainer: While there are many new controls added to the collection, I like SplitContainer control. You can use it to split any client area into two horizontal or vertical sections. By using nested split container controls you can easily divide main form area nicely and dock controls in them. 
  • Web browser Control: Web browser control comes with basic support to show contents of a URL. You can do few things easily but you may need to do some additional coding, if you need more browser features. You can use this control, to view any documents [Microsoft office, pdfs etc] inside your application. For this purpose it is very useful.

Learning curve

Your Skills Complexity Estimate (man days)
NEW Medium 7
.NET 1.1 Low 3

Developer Resources

STEP 2: Visual Studio .NET 2005

Visual Studio .NET 2005 IDE / Features

It is hard to think of developing .NET applications without using Visual Studio .NET and it is hard to find people who don't know Visual Studio. You can download Visual Studio .NET 2005 Expression edition for free OR Visual Studio .NET 2005, Professional Edition - 90 day trial, from Microsoft website, in case you have not yet looked at Visual Studio .NET 2005.

Visual Studio .NET, popular Integrated Development Environment from Microsoft, gets even better with 2005 version. Few annoying/missing features in 2003 version are fixed with 2005 release. One noticeable feature is that ASP.NET projects created on different machine can be opened without creating virtual folder first. 2005 version supports using file based web server making ASP.NET based development easy.

Refactor Code: Code refactoring support in Visual Studio can simplify few routine tasks (ex: build interface from existing class, it is really cool feature).

Code generation: Code snippets and generating interface implementation code templates are few useful features.

Please see resource section for more details on new features in VS 2005.

Publish/ClickOnce

Publish feature in Visual Studio 2005 is very useful and deploys windows forms project using ClickOnce deployment technology. Project can be deployed to FTP server, Web server, local file path OR network path. To use ClickOnce deployment technology, you need to do nothing when using Visual Studio 2005. Publish properties can be easily configured on Project properties page. Following figure shows publish tab on project properties page, in Visual Studio 2005:

SCA2.gif

Learning curve

Your Skills Complexity Estimate (man days)
NEW Medium 4
.VS 2003 Low 1

Developer Resources

STEP 3: Enterprise Library 2006

Application Blocks overview:

Application Blocks appeared during initial development of .NET 1.0/1.1 and they went through many revisions over the time. I think Microsoft got all this right this time with latest version Enterprise Library. 

What is Enterprise Library?

There are many common tasks that we do in each project: Logging, exception handling, database access, cache, authentication/authorization and encryption. Of course not every application may use all features but most of the features listed are useful for enterprise applications. Why to come up with custom solutions in each project, to address so common and standard problems in day-to-day life? Enterprise Library solves this, by creating application block for each development category. Application blocks can be looked as configurable class library to supports important application infrastructure like logging, caching, data access etc.

Following are 6 application blocks that are shipped with Enterprise Library 2006:

  1. Logging Application Block
  2. Exception Application Block
  3. Data Access Application Block
  4. Cache Application Block
  5. Cryptography Application Block
  6. Security Application Block

Following diagram is reproduced, from Microsoft Enterprise Library 2006 documentation for convenience and gives good picture of application blocks in Enterprise Library and their dependency.

SCA3.gif

Enterprise Library installation comes with excellent documentation and Hands on Labs for each block. So intentionally, in following sections, I am only showing the code required to use each application block. This should increase the comfort level for developers who are new to Enterprise library. Please see "Developer Resources" section for more details on how to get up to speed with Enterprise Library 2006.

Logging Application Block

Allows developers to log details using different application specific categories without worrying about where these details will be stored. Log configuration in application configuration file can be easily configured so that each log message can go to specific log listeners based on log category.

Sample code:

LogEntry logEntry = new LogEntry();
logEntry.EventId = 55;
logEntry.Priority = 1;
logEntry.Message =
"Started calaculation Process";
logEntry.Categories.Add(
"DEBUG");

Logger.Write(logEntry);

OR

Logger.Write("Starting Calculation", "DEBUG");

Logging block can be configured, to report critical errors to Event log, all debug statements to local file and other details to database. There is good flexibility and you can add your own listeners if you are not happy with what you get out of box and extend logging block.

Exception Application Block

Exception Application Block improves the process and policies around exception handling in an application. Configuration driven approach in exception block gives more flexibility around when to log exceptions, when to re-throw original exception OR modify exception etc.

Sample code:

try

{

     // Run code.

}

catch (Exception ex)

{

     bool rethrow = ExceptionPolicy.HandleException(ex, "ServerPolicy");

     if (rethrow)

          throw;

}

Data Access Application Block

Data Access Application Block allows writing database/provider agnostic code in ADO.NET. It is easy to use and actual provider can be changed in configuration. For example, if data access code initially is written using OLEDB ADO.NET provider for specific database, without any code change, application can switch to native ADO.NET provider for database (if and when one available). Developers need to be careful not to use provider specific types and should use ADO.NET interface types instead, for true database/provider agnostic code.

Sample code:

Database db = DatabaseFactory.CreateDatabase(); 

int cnt = db.ExecuteNonQuery(CommandType.Text, "select count(*) from authors");

Connection string and provider details are retrieved from Data Access Application Block configuration section.

Security Application Block

In many applications, it is common requirement to implement authentication (identity verification- who you are) and authorization (verifying what you can do - Roles) functionality. While requirement at high level is same, each application may choose to use different authentication stores and approaches to implement this functionality. Security application block helps us to write consistent code, transparent to actual authentication/authorization store.

Sample code:

Bool isLogin = Membership.ValidateUser("user", "pwd");

Above line authenticates the user, using default membership provider specified in the configuration.

Cryptography Application Block

It is common for applications to cache read-only and less frequently modified data, in memory. This is more common in server-side programs. There are many ways you can maintain the cache (ex: Hashtable, static members etc). Each application may take a different approach on when and how to refresh or expire cache data. Cache application block makes applying these strategies around Cache easy. Actual cache storage may still be few key/value pairs but additional support to apply cache strategies, makes this block useful. I found this block more useful in server side programming but you may find it useful scenarios for client applications too.

Sample code:

CacheManager cache = CacheFactory.GetCacheManager();

 

if(cache.Contains("EmployeeList"))

{

 

          EmployeeList lst = GetEmployeeList();

          cache.Add("EmployeeList", lst);

}

Cryptography Application Block

Supports one way encryption and two-way encryption. You can not decrypt one-way encrypted string and it is useful for encrypting passwords, credit card information etc. With two way encryption you can encrypt and decrypt the data. It is useful for storing connection string or other sensitive information in configuration files, which application needs in decrypted form, while running.

Sample code:

// Encrypt and decrypt

string b64data = Cryptographer.EncryptSymmetric("provider", "data");
string data = Cryptographer.DecryptSymmetric ("provider", b64data); 

// Get hash only

string pwdHash = Cryptographer.CreateHash("provider", "password");

It is recommended to use SHA256 (Secure Hash Algorithm) for Hash and AES [Advanced Encryption Standard] for symetric encryption. You can configure these options in the configuration for cryptography provider.

Object Builder/Design of Injection Model: We will not use ObjectBuilder directly but it is good to know the concepts. Later when (if) you read through CAB, understanding of ObjectBuilder might help.

Enterprise Library Configuration Management

Each application block requires different configuration sections and configuration data in application configuration file. It is difficult to remember all these details and modify application configuration by hand. To address these concerns, Enterprise Library has Configuration Manager tool, for managing configuration for all application blocks. You can use this tool and modify exsting application configuration file OR create new application configuration file. Enterprise Library Configuration Manager tool is very useful tool for developers and also administrators.

As you see from the figure below Enterprise Library Configuration Manager, makes it very easy to manage application block configration, for all the application blocks in the library.

SCA4.gif

NOTE: Enterprise Library application blocks design is very flexible and extensible. It is simple to use and that is true. Otherwise developers will not use Enterprise Library. But once you start digging deep, you will appreciate a lot of what people at Microsoft did. Also it comes with very good documentation, quick starts and hands on lab. What more you can expect for free?

Learning curve

Your Skills Complexity Estimate (man days)
NEW Medium 5
Used Application Blocks Low 2

Developer Resources

From my experience, you don't need any other reference documentation or samples than what you get out of box with "Enterprise Library 2006" and "Enterprise Library Hands On Labs 2006". Following are details how you can get up to speed with Enterprise Library.

    • Install "Enterprise Library 2006"
    • Go through Documentation/Help and Quick starts for each application block in the following order (You are free to pick up the order you like)
      • Logging Application Block
      • Exception Application Block
      • Data Access Application Block
      • Cache Application Block
      • Cryptography Application Block
      • Security Application Block
    • Install "Enterprise Library Hands On Lab 2006"
      • Hands on Labs are best way to get up to speed on new technology
      • Hands on Labs come with clear instructions of each step and it helps you understand what to do and why we are doing so.
      • Try the Hands on Labs in the above order for each application block.

STEP 4: CAB - Composite UI Application Block

For developers who used User Interface Process Application Block (UIP), CAB block may look familiar. But in this new version Microsoft Patterns and Practices group, did a lot of work to eliminate many drawbacks of UIP block and address few real world development issues better.

IMPORTANT: For user interface developers, learning this application block may be useful. Developers who focus more on server-side programming can skip till STEP 6 and continue from STEP 7. Another important note for user interface developers is, not to underestimate this application block. Understanding and getting hold of this application block, is not as easy as using application blocks from Enterprise Library. Main conceptual change required when using this block is, getting used to declarative/attribute based programming and decoupled thinking vs calling API and doing things more procedural way. It takes a while to learn this block and Microsoft Patterns and Practices group did a great job in providing excellent documentation and hands on labs to ease the learning.

On big relief for developers is that using new "Smart Client Software Factory" makes using CAB block very easy. But it won't cut down learning curve. It is important to understand core concepts of CAB block before you try any Smart Client Factory code. One other comfort is that not all developers need to know every aspect of CAB. You can have infrastructure group that really needs to understand all CAB aspects and you can have module developers who might only care knowing how to build modules. You will find all these details and enough guidance in CAB documentation.

Learning curve

Your Skills Complexity Estimate (man days)
NEW High 5
Used Application Blocks High 5

Developer Resources: 

  • Install "CAB - Composite UI Application Block"
  • Read documentation and QuickStart on following:  
    • Smart Part  
    • Event Broker  
    • Module Loader  
    • End-to-end Walkthroughs  
  • Install "CAB - Hands On Labs"
    • Try Hands on Lab in the sequence
    • You need to try at least 1 to 5.
    • Try the Hands on Labs in the above order for each application block.

NOTE: Most of the time when you use 3rd part controls in the UI, for core UI framework features like docking or navigation, you will end up extending CAB to create specific workspaces or UIAdapters. Workspaces and UIAdapters that come with CAB block are based on core Windows Forms controls and you may use them still in specific module views. Documentation and sample code that come with CAB block, provide enough guidance on how to extend CAB for this purpose.

STEP 5: Guidance Automation

For developing Windows Forms Smart Client Applications, developers need not know much about guidance automation other than how to use them in Visual Studio through Smart Client Factory. Guidance Automation is an extension to Visual Studio 2005. Please see "Developer Resources" section for installation details of Guidance Automation and Extension Toolkit.

It is normal practice that architects and team leaders come up with design/development standards and document coding guidelines to developers in the team. This helps keeping the code consistent and following best practices. But unfortunately not all developers have same skills and exposure to technology. It is difficult to document every detail and expect all developers to read documentation and follow the guidelines.  This is where Guidance automation come for rescue.

With Guidance packages architects can create a fully functional project/solution templates and/or code blocks, following project guidelines to help developers with intermediate steps. Developers can easily create projects and code from any guidance package, available to them in Visual Studio 2005. It is very powerful and going forward this might help a lot in sharing reusable code-blocks and other standard templates across organization, to keep the code consistent. When you use Smart Client Factory, you will see the power of guidance packages in action.

Developer Resources:

  • Install Guidance Automation and Extension Toolkit
  • Read through the help/documentation [included in the installation]
  • If you are interested in creating guidance packages for your organization, you will find many useful resources on the Internet. You don't need to know how to create new packages if you have happy with Smart Client Factory guidance package [see STEP 6]

STEP 6: Smart Client Software Factory

So far we covered Enterprise Library application blocks and CAB block along with.NET 2.0 framework and Visual Studio 2005. Just by using these new tools/technology we don't get the benefit, unless we understand all the concepts and use them as per guidelines. Even with all the documentation and sample code provided, it is easy to make mistakes. Also it takes longer to understand new technology and use it the right away in today's time pressured environments. For this reason Microsoft Patterns and Practices group is creating Software Factories. Because code is generated by the package it looks consistent across and it leaves even smaller room for error.

Microsoft Practices and Patterns group created Smart Client Factory using Guidance Automation in Visual Studio 2005. So Smart Client Software Factory is a factory, to create key software components and automate many repetitive tasks during the development. Smart Client Software Factory is not a library OR API but rather guidance package to simply using Composite UI Application Block. So if you are planning to build your windows forms based application with Composite UI Application Block, using Smart Client Software Factory saves a lot of headache.

Smart Client Software Factory also improves productivity and understanding of using CAB block for developers. Following are few examples that might give you some quick view of what Smart Client Factory can do:

SCA5.gif

When new Smart Client Application is created using "Smart Client Software Factory" Guidance package, following solution is created with list of projects shown in the figure.

SCA6.gif

MVC vs. MVP: Developers who used CAB block and tried "Hands On Labs and Quick Walkthrough" of CAB are familiar with MVC (Model-View-Controller) pattern. For easy of use and simplicity (I think), Smart Client Factory samples and reference application that you see, use MVP (Model/View/Presenter) design pattern. You may find it little odd but the concepts you learned with MVC approach in CAB, are still useful. Following diagram reproduced from Smart Client Software Factory documentation for convenience, to show the different between MVC and MVP patterns:

SCA7.gif

Learning curve

Your Skills Complexity Estimate (man days)
NEW Meduim 4
Used Guidance packages Low 2

Developer Resources:

  • Getting Started
  • Install Smart Client Software Factory
  • Read through the documentation/help [part of installation]
  • Try the end-to-end reference applications [part of installation]
  • Try creating new Smart Client Project using Smart Client Software Factory

STEP 7: Useful tools and future .NET releases

There are few 3rd party tools that might be useful in helping developers in specific areas of testing/debugging etc. NUnit is good unit testing framework and Visual Studio 2005 also support writing Unit tests. Following are few useful links that developers might find interesting:

Table below lists few new technologies and buzz words in the industry. It helps to be familiar with these terms. Mostly getting exposure to what they mean is good.

Technology/Buzz word Description/Comment
SOA Service Oriented Architecture
.NET 3.0 Next version of .NET framework. Recently Microsoft announced that WCF [Windows Communication Framework], WFS [Windows Workflow Foundation Service], WPF [Windows Presentation Framework] and few other core sub-systems will be part of .NET 3.0 framework.
WCF/Indigo Windows Communication Framework [Indigo] - You can say it is next version of Web services.
WPF/Avalon Windows Presentation Framework [Avalon]- This is really cool and at present this is not a replacement of Windows Forms system. CAB block is designed to handle both Windows Forms and WPF applications [as per CAB documentation]
AJAX Asynchronous Javascript And XML. This is useful when building websites and not so much for Windows Forms Applications. There are many vendors building web controls with AJAX support. With Smart Client we get ease of deployment of web applications, in Windows Forms applications. With AJAX it is other way, we can some richer user experience of Windows Forms application in web applications.
WFS Windows Workflow Foundation Service - seems promising so far and may be useful for workflow applications. From what I heard, we might be able to use rules engine and scheduler of Workflow Foundation Service stand-alone.
Vista/Longhorn New Windows Operating System from Microsoft

Conclusion

It is not easy to cover various technology details in one article. Intentionally I did not go deep into details of any specific topic. Main objective of this article is to help everyone including developers who are new to .NET, to get quick exposure through guided tour of recent technology/tools. Intention is to raise the comfort level and enthusiasm of the reader, to learn new tools/technologies. Nothing substitutes self-learning and writing code to tryout the technology. There is wealth of information provided in this article in "Developer Resource" section under each technology area, which should help in gaining further understanding on any technology.

Also many times I get emails from few people asking for guidance on how they can improve their skills. It is tough to answer these questions without knowing individual skill level and what they want to be. I hope I tried to address this issue better in this article by providing clear roadmap to developers based on their current skill level.

DISCLAIMER: Views expressed in this article are totally based on my work experience, technology exposure and are my personal views. They should not be viewed as views of Microsoft or any other company/vendor. URLs/Resource links provided in this document may navigate the reader to different website. These links are provided only to help the reader to get up to speed on new technology. While it is not intentional and I try my best to provide accurate details, if you still find any mistakes please send them to me.

COMMENT USING
Employers - Post Free Jobs