-
Do you have a consistent .NET Solution Structure?
When developing a n-tiered software solution, we follow a standard solution structure.
We have incorporated unit testing components, which is an integral part of the Extreme
Programming development methodology, into our solution structure:
|
Project Type
|
Project Name
|
|
Note
|
|
Application
|
Northwind
|
|
Namespace:
|
SSW.Northwind
|
|
Folder:
|
SSW\Northwind\Northwind\
|
|
Output:
|
Northwind.exe
|
|
|
|
Class Library
|
WindowsUI
|
|
Namespace:
|
SSW.Northwind.WindowsUI
|
|
Folder:
|
SSW\Northwind\WindowsUI\
|
|
Folder:
|
SSW\Northwind\WindowsUI\Components
|
|
Folder:
|
SSW\Northwind\WindowsUI\UnitTests
|
|
Output:
|
WindowsUI.dll
|
|
We put all the forms in a separate project so we can run Unit Tests on the UI using
reflection.
|
|
Application
|
ConsoleUI
|
|
Namespace:
|
SSW.Northwind.ConsoleUI
|
|
Folder:
|
SSW\Northwind\ConsoleUI\
|
|
Folder:
|
SSW\Northwind\ConsoleUI\Components
|
|
Folder:
|
SSW\Northwind\ConsoleUI\UnitTests
|
|
Output:
|
NorthwindConsole.exe
|
|
|
|
Application
|
WebUI
|
|
Namespace:
|
SSW.Northwind.WebUI
|
|
Folder:
|
SSW\Northwind\WebUI\
|
|
Folder:
|
SSW\Northwind\WebUI\Components
|
|
Folder:
|
SSW\Northwind\WebUI\UnitTests
|
|
Output:
|
SSW.Northwind.WebUI.dll
|
|
|
|
|
|
|
Namespace:
|
SSW.Northwind.WebUI.Reports
|
|
Folder:
|
SSW\Northwind\WebUI\Reports\
|
|
Manually-based reports - e.g. using the DataGrid
|
|
|
|
|
Namespace:
|
SSW.Northwind.WebUI.Components
|
|
Folder:
|
SSW\Northwind\WebUI\Components\
|
|
Part of WebUI. For .css and .ascx user controls
|
|
Windows Service
|
WindowsService
|
|
Namespace:
|
SSW.Northwind.WindowsService
|
|
Folder:
|
SSW\Northwind\WindowsService\Components
|
|
Folder:
|
SSW\Northwind\WindowsService\UnitTests
|
|
Output:
|
SSW.Northwind.WindowsService.dll
|
|
|
|
RS Reports
|
Reports
|
|
Namespace:
|
N/A
|
|
Folder:
|
SSW\Northwind\Reports
|
|
Output:
|
N/A
|
|
Reporting Services
|
|
Class Library
|
IServices
|
|
Namespace:
|
SSW.Northwind.IServices
|
|
Folder:
|
SSW\Northwind\IServices
|
|
Folder:
|
SSW\Northwind\IServices\UnitTests
|
|
Output:
|
SSW.Northwind.IServices.dll
|
|
WCF Services Interfaces
|
|
Class Library
|
Services
|
|
Namespace:
|
SSW.Northwind.Services
|
|
Folder:
|
SSW\Northwind\Services
|
|
Folder:
|
SSW\Northwind\Services\UnitTests
|
|
Output:
|
SSW.Northwind.Services.dll
|
|
WCF Services Implementations
|
|
Class Library
|
Business
|
|
Namespace:
|
SSW.Northwind.Business
|
|
Folder:
|
SSW\Northwind\Business\
|
|
Folder:
|
SSW\Northwind\Business\Components
|
|
Folder:
|
SSW\Northwind\Business\UnitTests
|
|
Output:
|
SSW.Northwind.Business.dll
|
|
This can be code-generated
|
|
Class Library
|
Domain
|
|
Namespace:
|
SSW.Northwind.Domain
|
|
Folder:
|
SSW\Northwind\Domain
|
|
Folder:
|
SSW\Northwind\Domain\UnitTests
|
|
Output:
|
SSW.Northwind.Domain.dll
|
|
LINQ DBML - this can be generated using SQL Metal
|
|
Class Library
|
DataSets
|
|
Namespace:
|
SSW.Northwind.DataSets
|
|
Folder:
|
SSW\Northwind\DataSets\
|
|
Folder:
|
SSW\Northwind\DataSets\UnitTests
|
|
Output:
|
SSW.Northwind.DataSets.dll
|
|
Strongly typed datasets - this can be code-generated
|
|
Class Library
|
DataAccess
|
|
Namespace:
|
SSW.Northwind.DataAccess
|
|
Folder:
|
SSW\Northwind\DataAccess\
|
|
Folder:
|
SSW\Northwind\DataAccess\Components
|
|
Folder:
|
SSW\Northwind\DataAccess\UnitTests
|
|
Output:
|
SSW.Northwind.DataAccess.dll
|
|
This project should contain all the code and SQL statements used to access data from
your backend. This project can be code-generated
|
|
Class Library
|
UnitTests
|
|
Namespace:
|
SSW.Northwind.UnitTests
|
|
Folder:
|
SSW\Northwind\UnitTests\
|
|
Output:
|
SSW.Northwind.UnitTests.dll
|
|
Only need this project if you are not using reusable components and then you do
not need UnitTests folders above
|
|
Wise Setup
|
Northwind
|
|
Folder:
|
SSW\Northwind\Setup\
|
|
Output:
|
SSWNorthwind_v1-11.exe
|
|
Make an EXE in Wise intead of an MSI because it allows the application to be upgraded
|
We have included the unit tests with the project the test is designed for together
into one assembly for several reasons:
- It provides a logical association between the test and the class it is designed
to test, promoting consistency between the two.
- Ease of management - we don't need to match test assembly with the actual assembly
by file naming standards, the association is inherent.
- There are less projects to compile in a solution.
For common library project, project name should include the company prefix and solution
name, this is so other internal solution can include the common library's project.
This will help debugging and development processes.
|
Project Type
|
Project Name
|
|
Note
|
|
Class Library
|
SSWCommon Business
|
|
Namespace:
|
SSW.Common.Business
|
|
Folder:
|
..\SSW\Common\Business\
|
|
Output:
|
SSW.Common.Business.dll
|
|
No space in the Project Name
|
For project documents, we should also add them into solution for later reference,
and different document types will be put in different folder, e.g. Artworks' files
will be in SSW\Documents\ArtWorks\
|
Project Type
|
Project Name
|
|
Note
|
|
Documents
|
Documents
|
|
This is outside the solution trunk
|
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/SolutionStructure.aspx
-
Do you name your startup form consistently?
In every Windows application project. We need to have a main form for a better structure
and design.
-

-
Bad example - The entry form is not immediately recognizable because of a non standard
name
-

-
Good example - The entry form follows the naming convention rule
|
We have a program called SSW Code
Auditor to check for this rule.
Note: In Code Auditor we check for Form named: Startup, MainService, MainForm and
WizardPage.
|
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/NameStartupFormConsistently.aspx
-
Do you have a structured Solution Folders
for your solution items?
All the DLL references and files needed to create a setup.exe should be included in
your solution. However, just including them as solution items isnt enough, they
will look very disordered (especially when you have a lot of solution items). And
from the screenshot below, you might be wondering what the _Instructions.txt is
used for...
-

-
Bad example - An unstructured solution folder
An ideal way is to create "sub-solution folders" for the solution items, the common
ones are "References" and "Setup". This feature is only available in Visual Studio
2005. This will make your solution items look neat and in order. Look at the screenshot
below, now it makes sense, we know that the _Instructions.txt contains the instructions
of what to do when creating a setup.exe.
-

-
Good example - A well structured solution folder has 2 folders - "References" and
"Setup"
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/SolutionFolders.aspx
-
Do you always say "Option Strict On"?
Fixing the Option Strict problem One of the most annoying aspects of the Visual
Basic development environment relates to Microsofts decision to allow late binding.
By turning Option Strict Off by default, many type-casting errors are not caught
until runtime. You can make VB work the same as other MS languages (which always
do strict type-checking at design time) by modifying these templates.
So, always set Option Strict On right from the beginning of the development.
Before you do this, you should first back up the entire VBWizards directory. If you
make a mistake, then the templates will not load in the VS environment. You need
to be able to restore the default templates if your updates cause problems.
To configure each template to default Option Strict to On rather than Off, load
each .vbproj template with VB source code into an editor like Notepad and then change
the XML that defines the template. For example, to do this for the Windows Application
template, load the file: Windows Application\Templates\1033\WindowsApplication.vbproj
under the VBWizards directory into Notepad and find the settings Element. You should
see something like this:
<VisualStudioProject>
<VisualBasic>
<Build>
<Config . . . .
Now, add the following lines under OutputType: OptionStrict = "On" OptionExplicit
= "On"
Or you go to Project property page as you can see the snapshot below and change
to Option Strict = "On", OptionExplicit = "On".
-
Technically, you do not have to add the Option Explicit directive, because this
is the default for VB; but I like to do it for consistency. Next, you must save
the file and close Notepad. Now, if you load a new Windows Application project in
the VS environment and examine Project Properties, you will see that Option Strict
has been turned on by default.
In order for this setting to take effect for all project types, you must update
each of the corresponding .vbproj templates. After making the changes on your system,
youll need to deploy the new templates to each of your developers' machines in order
for their new projects to derive from the updated templates.
However, sometimes we don't do this because of too much work. In some scenarios,
such as Wrappers around the COM code, and Outlook stuff with object model, there
is going to be lots of work to fix all the type-checking errors. Actually it is
necessary to use Object type as parameters or variables when you deal with COM components.
.NET Standards in ZDNet
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/OptionStrictOn.aspx
-
Do you keep clean on Imports of Project Property?
When programming in a Dot Net environment it is a good practice to remove the default
imports that aren't used frequently in your code.
This is because IntelliSense lists will be harder to use and navigate with too many
imports. For example if in VB.NET, Microsoft.VisualBasic would be a good item to
have in the imports list, because it will be used in most areas of your application.
To remove all the default imports, load Project Property page and select Common
properties - Imports.
-
Using Aliases with the Imports Statement :
The Import statement makes it easier to access methods of classes by eliminating
the need to explicitly type the fully qualified reference names. Aliases let
you assign a friendlier name to just one part of a namespace.
For example, the carriage return-line feed sequence that causes a single piece of
text to be displayed on multiple lines is part of the ControlChars class in the
Microsoft.VisualBasic namespace. To use this constant in a program without an alias,
you would need to type the following code:
-
MsgBox("Some text" & Microsoft.VisualBasic.ControlChars.crlf _ &
"Some more text")
Imports statements must always be the first lines immediately following any Option
statements in a module. The following code fragment shows how to import and assign
an alias to the Microsoft.VisualBasic.ControlChars namespace:
-
Imports CtrlChrs=Microsoft.VisualBasic.ControlChars
Future references to this namespace can be considerably shorter:
-
MsgBox("Some text" & CtrlChrs.crlf & "Some more text")
If an Imports statement does not include an alias name, elements defined within
the imported namespace can be used in the module without qualification. If the alias
name is specified, it must be used as a qualifier for names contained within that
namespace.
Figure: Using aliases with the Imports Statement
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/ImportsOfProjectProperty.aspx
-
Do you add the necessary code so you can always sync the
web.config file?
The Web.config file should be your main source where you store your application settings.
These change, depending on which system you are working on, e.g. your local machine
or the website. That's why you have to keep two versions of the Web.config file,
one for your local machine and one for the website.
That's annoying, not really efficient and often the cause of problems.
In the following extract of a sample Web.config file you can see the problem. The
local machine "HIPPO" has, of course, another WebServiceURL than the Webserver
"SEAL". So you have to keep two versions of the Web.config file, one when
working on "HIPPO" and one when working on "SEAL".
-
<add key="SEAL_WebServiceURL" value="http://host.something.com:80/SomeDirectory/Filename.asmx"/>
<add key="HIPPO_WebServiceURL"value="http://name:80/SomeDirectory/Filename.asmx"/>
Figure: Sample Web.config file
There is a better solution:
-
Public Shared Function GetWebConfigString(ByVal StringName As String) As String
Dim strReturn As String = ""
Dim strComputerName As String = System.Net.Dns.GetHostName
Try
strReturn = ConfigurationSettings.AppSettings( strComputerName.ToUpper + "_"+ StringName)
Catch
strReturn = ConfigurationSettings.AppSettings(StringName)
End Try
Return strReturn
End Function
Figure: Sample Get WebConfigString Class
This class simply adds the name of the Computer on which it is running on to the
WebConfigString. In the former example, this would be "HIPPO_" or "SEAL_".
Instead of using the WebConfigString directly you can now transform it using this
function. With the help of this code, you always get the right value for the WebConfigString,
no matter on which machine the application runs and you don't have to care about
synchronizing the Web.config file any more.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/SyncWebConfigFile.aspx
Do you avoid periods (.) in directory names?
We've elected to not allow periods in directory name, primarily because it creates
problems with Windows' auto-complete intellisense.
|
C:\Bad.Example.With.Periods\
|
Figure: Bad example of directory name with periods (.)
|
C:\Good Example Without Periods\
|
Figure: Good example of directory name without periods (.)
-
Do you name your assemblies consistently (<CompanyName>.<ComponentName>)?
Assembly names should reflect the the functionality that it provides. For example,
-
System.IO
contains all the classes that deal with inputs and outputs. As a general rule of
thumb your assemblies should be named as follows:
<CompanyName>.<ComponentName> (e.g. SSW.Framework)
This allows a developer to know who developed the assembly and give the developer
a general idea of what the assembly can be used for.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/AssemblyName.aspx
-
Do you use the designer for all visual elements?
The designer should be used for all GUI design. Controls will be dragged and dropped
onto the form and all properties should be set in the designer, e.g.
- Labels, TextBoxes and other visual elements
- ErrorProviders
- DataSets (to allow data binding in the designer)
Things that do not belong in the designer:
- Connections
- Commands
- DataAdapters
However, and DataAdapter objects should not be dragged onto forms, as they
belong in the business tier. Strongly typed DataSet objects should be in the
designer as they are simply passed to the business layer. Avoid writing code for
properties that can be set in the designer.
-
-
Bad example - Connection and Command objects in the Designer
-
-
Good example - Only visual elements in the designer
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/DesignerForAllVisualElements.aspx
-
Do you refer to images the correct way in ASP .NET?
There are many ways to reference images in ASP.NET. There are two different situations
commonly encountered by developers when working with images:
- Scenario #1: Images that are part of the content of a specific page eg. a picture
used only on one page
- Scenario #2:Images that are shared across on user controls which are shared across
different pages in a site eg. a shared logo used across the site (commonly in user
controls, or master pages)
Each of these situations requires a different referencing method.
Option #1:Absolute Paths (Root-Relative Paths)
Often developers reference all images by using an absolute path (prefixing the path
with a slash, which refers to the root of the site), as shown below.
-
<img src="/Images/spacer.gif" height="1" width="1">
-
Bad example - Referencing images with absolute paths
This has the advantage that <img> tags can easily be copied between pages,
however it should not be used in either situation, because it requires that the website
have its own site IIS and be placed in the root (not just an application), or that
the entire site be in a subfolder on the production web server. For example, the
following combinations of URLs are possible with this approach:
|
Staging Server URL
|
Production Server URL
|
|
http://bee:81/
|
http://www.ssw.com.au/
|
|
http://bee/ssw/
|
http://www.ssw.com.au/ssw/
|
As shown above, this approach makes the URLs on the staging server hard to remember,
or increases the length of URLs on the production web server.
Verdict for Scenario #1:

Verdict for Scenario #2:

Option #2:Relative Paths
Images that are part of the content of a page should be referenced using relative
paths, e.g.
-
<img src="../Images/spacer.gif" height="1" width="1">
-
Good example - Referencing images with absolute paths.
However, this approach is not possible with images on user controls, because the
relative paths will map to the wrong location if the user control is in a different
folder to the page.
Verdict for Scenario #1:

Verdict for Scenario #2:

Option #3:Application-Relative Paths
In order to simplify URLs, ASP.NET introduced a new feature, application relative
paths. By placing a tilde (~) in front of a path, a URL can refer to the root of
a site, not just the root of the web server. However, this only works on Server
Controls (controls with a runat="server" attribute).
To use this feature, you need either use ASP.NET Server controls or HTML Server
controls, as shown below.
-
<asp:Image ID="spacerImage" ImageUrl="~/Images/spacer.gif"
Runat="server" />
<img id="spacerImage" src="~/Images/spacer.gif" runat="server">
-
Good example - Application-relative paths with an ASP.NET Server control
Using an HTML Server control creates less overhead than an ASP.NET Server control,
but the control does not dynamically adapt its rendering to the user's browser,
or provide such a rich set of server-side features.
Verdict for Scenario #1:

Verdict for Scenario #2:

Note:A variation on this approach involves calling the Page.ResolveUrl method with
inline code to place the correct path in a non-server tag.
-
<img src='<%# Page.ResolveUrl("~/Images/spacer.gif") %>'>
-
Bad example - Page.ResolveUrl method with a non-server tag
This approach is not recommended, because the data binding will create overhead
and affect caching of the page. The inline code is also ugly and does not get compiled,
making it easy to accidentally introduce syntax errors.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/ReferToImagesCorrectlyInASPDotNET.aspx
-
Do you use Microsoft.VisualBasic.dll for Visual Basic.NET projects?
The Microsoft.VisualBasic library is provided to ease the implementation of the
VB.NET language itself. For VB.NET, it provides some methods familiar to the VB
developers and can be seen as a helper library. It is a core part of the .NET redistribution
and maps common VB syntax to framework equivalents, without it some of the code
may seem foreign to VB programmers.
|
Microsoft.VisualBasic
|
.NET Framework
|
|
CInt, CStr
|
Convert.ToInt(...), ToString()
|
|
vbCrLf
|
Environment.NewLine, or "\r\n"
|
|
MsgBox
|
MessageBox.Show(...)
|
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/UseMSVBDllForVBNETProjects.aspx
-
Do you avoid Microsoft.VisualBasic.Compatibility.dll
for Visual Basic.NET projects?
This is where you should focus your efforts on eliminating whatever VB6 baggage your
programs or developer habits may carry forward into VB.NET. There are better framework
options for performing the same functions provided by the compatibility library
You should heed this warning from the VS.NET help file: Caution: It is not recommended
that you use the VisualBasic.Compatibility namespace for new development in Visual
Basic .NET. This namespace may not be supported in future versions of Visual Basic.
Use equivalent functions or objects from other .NET namespaces instead.? ad.?
Avoid:
- InputBox
- ControlArray
- ADO support in Microsoft.VisualBasic.Compatibility.Data
- Environment functions
- Font conversions
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/AvoidMSVBCompatibilityDllForVBNETProjects.aspx
-
Do you publish your components to Source Safe?
Incrementally as we do more and more .NET projects, we discover that we are re-doing
a lot of things we've done in other projects. How do I get a value from the
config file? How do I write it back? How do I handle all my uncaught exceptions
globally and what do I do with them?
Corresponding with Microsoft's release of their application blocks, we've
also started to build components and share them across projects.
Sharing a binary file with SourceSafe isn't a breeze to do, and here are the
steps you need to take. It can be a bit daunting at first.
As the component developer, there are four steps:
- In Visual Studio.NET, Switch to release build
-

Figure: Switch to release configuration
- In your project properties, make sure the release configuration goes to the bin\Release?
folder. While you are here, also make sure XML docs are generated. Use the same
name as your dll but change the extension to .xml (eg. for SSW.Framework.Configuration.dll
-> add SSW.Framework.Configuration.xml)
-

Figure: Project properties
Note: The following examples are considered being used for C#. Visual Basic, by
default, does not have \bin\Release and \bin\Debug which which means that the debug
and release builds will overwrite each other unless the default settings are changed
to match C# (recommended). VB does not support XML comments either, please wait
for the next release of Visual Studio (Whidbey).
-

Figure: Force change to match C#
- If this is the first time, include/check-in the release directory into your SourceSafe
-

Figure: Include the bin\Release directory into source safe
- Make sure everythings checked-in properly. When you build new versions, switch to
Release?mode and checkout the release dlls, overwrite them, and when you check them
back in they will be the new dll shared by other applications.
-
If the component is part of a set of components, located in a solution, with some
dependency between them. You need to check out ALL the bin\Release folders for all
projects in that solution and do a build. Then check in all of them. This will ensure
dependencies between these components don't conflict with projects that reference
this component set.
In other words, a set of components such as SSW.Framework.WindowsUI.xxx, increment
versions AS A WHOLE. One component in this set changes will cause the whole set
to re-establish internal references with each other.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/PublishComponentsToSourceSafe.aspx
-
Do you reference "most" .dlls by Project?
When you obtain a 3rd party .dll (in-house or external), you sometimes get the code
too. So should you:
- reference the Project (aka including the source) or
- reference the assembly?
When you face a bug, there are 2 types of emails you can send:
- Dan, I get this error calling your Registration.dll? or
- Dan, I get this error calling your Registration.dll and I have investigated it.
As per our conversation, I have changed this xxx to this xxx.
The 2nd option is preferable.
The simple rule is:
If there are no bugs then reference the assembly, and
If there are bugs in the project (or any project it references [See note below])
then reference the project.
Since most applications have bugs, therefore most of the time you should be using
the second option.
If it is a well tested component and it is not changing constantly, then use the
first option.
- Add the project to solution (if it is not in the solution).
-

Figure: Add existing project
- Select the "References" folder of the project you want to add references to, right
click and select "Add Reference...".
-

Figure: Add reference
- Select the projects to add as references and click OK.
-

Figure: Select the projects to add as references
Note: We have run into a situation where we reference a stable project A, and an
unstable project B. Project A references project B. Each time project B is built,
project A needs to be rebuilt.
Now, if we reference stable project A by dll, and unstable project B by project
according to this standard, then we might face referencing issues, where Project
A will look for another version of Project B ?the one it is built to, rather than
the current build, which will cause Project A to fail.
To overcome this issue, we then reference by project rather than by assembly, even
though Project A is a stable project. This will mitigate any referencing errors.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/ProjectReferencing.aspx
-
Do you reference "very calm/stable" .dlls by Assembly?
If we lived in a happy world with no bugs, I would be recommending this approach
of using shared components from source safe. As per the prior rule, you can see
we like to reference "most" .dlls by project.
However if you do choose to reference a .dll without the source, then the important
thing is that if the .dll gets updated by another developer, then there is *nothing*
to do for all other developers ?they get the last version when they do your next
build. Therefore you need to follow this:
As the component user, there are six steps, but you only need to do them once:
- First, we need to get the folder and add it to our project, so in SourceSafe, right
click your project and create a subfolder using the Create Project (yes, it is very
silly name) menu.
-

Figure: Create 'folder' in Visual Source Safe
Name it References
-

Figure: 'References' folder
- Share the dll from the directory, so if I want SSW.Framework.Configuration, I go
to $/ssw/SSWFramework/Configuration/bin/Release/
I select both the dll and the dll.xml files, right-click and drag them into my $/ssw/zzRefs/References/
folder that I just created in step 1.
-

Figure: Select the dlls that I want to use
-

Figure: Right drag, and select "Share"
- Still in SourceSafe, select the References folder, run get latest?to copy the latest
version onto your working directory.
-

Figure: Get Latest from Visual Source Safe
VSS may ask you if you want to create the folder, if it doesnt exist. Yes, we do.
- Back in VS.NET, select the project and click the show-all files button in the solution
explorer, include the References folder into the project (or get-latest if its already
there)
-

Figure: Include the files into the current project
- IMPORTANT! If the files are checked-out to you when you include them into your project,
you MUST un-do checkout immediately.
You should never check in these files, they are for get-latest only.
-

Figure: Undo Checkout, when VS.NET checked them out for you...
- Add Reference?in VS.NET, browse to the References?subfolder and use the dll there.
- IMPORTANT! You need to keep your 'References' folder, and not check the
files directly into your bin directory. Otherwise when you 'get latest',
you won't be able to get the latest shared component.
All done. In the future, whenever you do get-latest?on the project, the any updated
dlls should come down and be linked the next time you compile. Also, if anyone checks
out your project from Source Safe, they will have the project linked and ready to
go.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/ReferenceAssembly.aspx
-
Do you keep your Assembly Version Consistent?
It is important to keep Assembly version and Assembly file version consistent, otherwise
it can lead to support and maintenance nightmares. By default these version values
are defined in the AssemblyInfo file. In the following examples, the first line
is the version of the assembly and the second line is the actual version display
in file properties.
-
[assembly: AssemblyVersion("2.0.*")]
[assembly: AssemblyFileVersionAttribute("1.0.0.3")]
-
Bad example - the common assembly versioning method.
-
[assembly: AssemblyVersion("2.0.*")]
[assembly: AssemblyFileVersionAttribute("2.0.*")]
-
Good example - the best way for Assembly versioning.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/KeepAssemblyVersionConsistent.aspx
-
Do you use configuration management application
block?
How do you get a setting from a configuration file? What do you do when you want
to get a setting from a registry, or a database? Everyone faces these problems,
and most people come up with their own solution. We used to have a few different
standards, but when Microsoft released the Configuration Application Blocks, we
have found that working to extend it and use it in all our projects saves us a lot
of time! Use a local configuration file for machine and/or user specific settings
(such as a connection string), and use a database for any shared values such as
Tax Rates.
See how we configured this reset default settings functionality with the Configuration
Block in the .NET Toolkit
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/ConfigurationManagementAppBlock.aspx
-
Do you have a resetdefault() function in your configuration
management application block?
In almost every application we have a user settings file to store the state of the
application. We want to be able to reset the settings if anything goes wrong.
See how we configured this reset default settings functionality with the Configuration
Block in the .NET Toolkit
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/ResetDefault.aspx
-
Do you hard code your ConnectionString?
We don't like hard coded string inside our programme. We are using model-driven
development, in which we create or reuse code, and perform changes in configuration
file rather the in-code changing. More information on implementing
our configuration.
-
connection.ConnectionString = "
Provider=SQLOLEDB;
Data Source=server_name_or_address; Initial Catalog=database_name;
User ID=username; Password=password; ";
connection.Open();
-
Bad code - use the lengthy connection string.
-
connection.ConnectionString = ConfigurationManager.Items["ConnectionString"];
connection.Open();
-
Figure: Good Code - Use ConfigurationManager to handle the connection string.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/HardCodeConnectionString.aspx
-
You get an error message reported from a user like:
When I click the Save button on the product form it gives an error message about
a missing field.
You try and reproduce it on your version in the office and everything works perfectly,
you suspect that the customer probably has changed the schema. So you start drafting
an email to the user like:
Mary, I need you to send me your database schema as it might be different from what
it should be. Can you:
- Open up Enterprise Manager (or SQL Management Studio)
- Open the first tree
- Open the second tree
- Select your server
- Open that tree
- Select Databases
- Open that tree
- Select the database called Northwind
- Right click it and choose All Tasks, then Generate SQL Script
- Then select the options
- etc
- Then when I get this I will compare and I will make a script file for you to
run and fix the problem
STOP! STOP! STOP!
It would be much better to just say:
Mary, click the "Reconcile" button and it will tell us what is wrong
Bottom line is the customers' database schema will always be correct and this should
be managed automatically by the application.
Therefore, we always deliver an application with the buttons "Create", Upgrade"
and "Reconcile", accessible via "Tools - Options" and a "Database" tab. We do this
by using SSW SQL Deploy and throwing on the inherited user-control from the SSW.SQLDeploy.Options
project.
For more information see
Best Tools for SQL Server
It looks like this
-

Figure: Showing the "Reconcile" which compares the current scripts with
the Clients database; and the "Upgrade" which will run the scripts that
have been most recently included in the latest version for the client.
On clicking "Select" another Dialog will open with the required functions
for creating a database or using an existing one as shown below:
-
Figure: Creating a New database for the client if there is no database to begin with.
As a developer, I promise to do these 3 things:
- Save every SQL change I do as a script
- Make sure the application I develop, has 3 buttons, "Create", "Update" and "Reconcile"
- Never ask a client to run a script
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterSQLServerSchemaDeployment/Pages/DoYouMakeSureThatTheDatabaseStructureIsAutomaticallyHandledAutomaticallyVia3ButtonsCreateUpgradeAndReconcile.aspx
-
It is good to store program settings in an .xml file. But developers rarely worry
about future schema changes and how they will inform the user it is an old schema.
What is wrong with this?
-
<?xml version="1.0" standalone="yes"?>
<NewDataSet>
<xs:schema id="NewDataSet" xmlns=""
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name=NewDataSet" msdata:IsDataSet="true" msdata:Locale="en-AU">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="Table1">
<xs:complexType>
<xs:sequence>
<xs:element name="DateUpdated" type="xs:dateTime" minOccurs="0" />
<xs:element name="NewDatabase" type="xs:boolean" minOccurs="0" />
<xs:element name="ConnectionString" type="xs:string" minOccurs="0" />
<xs:element name="SQLFilePath" type="xs:string" minOccurs="0" />
<xs:element name="TimeOut" type="xs:int" minOccurs="0" />
<xs:element name="TurnOnMSDE" type="xs:boolean" minOccurs="0" />
<xs:element name="KeepXMLRecords" type="xs:boolean" minOccurs="0" />
<xs:element name="UserMode" type="xs:boolean" minOccurs="0" />
<xs:element name="ReconcileScriptsMode" type="xs:boolean" minOccurs="0" />
<xs:element name="FolderPath" type="xs:string" minOccurs="0" /> />
<xs:element name="SelectedFile" type="xs:string" minOccurs="0" />
<xs:element name="UpdateVersionTable" type="xs:boolean" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
<Table1>
<DateUpdated>2004-05-17T10:04:06.9438192+10:00</DateUpdated>
<NewDatabase>true</NewDatabase>
<ConnectionString>Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Data Source=(local);Initial Catalog=master</ConnectionString>
<SQLFilePath>ver0001.sql</SQLFilePath>
<TimeOut>5</TimeOut>
<TurnOnMSDE>false</TurnOnMSDE>
<KeepXMLRecords>false</KeepXMLRecords>
<UserMode>true</UserMode>
<ReconcileScriptsMode>true</ReconcileScriptsMode>
<FolderPath>C:\Program Files\SSW SQL Deploy\Samples\DatabaseSQLScripts\</FolderPath>
<SelectedFile />
<UpdateVersionTable>true</UpdateVersionTable>
</Table1>
</NewDataSet>
-
Bad example - XML file without version control.
-
<?xml version="1.0" standalone="yes"?>
<NewDataSet>
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http:/www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:Locale="en-AU">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="Table1">
<xs:complexType>
<xs:sequence>
<xs:element name="Version" type="xs:string" minOccurs="0" />
<xs:element name="DateUpdated" type="xs:dateTime" minOccurs="0" />
<xs:element name="NewDatabase" type="xs:boolean" minOccurs="0" />
<xs:element name="ConnectionString" type="xs:string" minOccurs="0" />
<xs:element name="SQLFilePath" type="xs:string" minOccurs="0" />
<xs:element name="TimeOut" type="xs:int" minOccurs="0" />
<xs:element name="TurnOnMSDE" type="xs:boolean" minOccurs="0" />
<xs:element name="KeepXMLRecords" type="xs:boolean" minOccurs="0" />
<xs:element name="UserMode" type="xs:boolean" minOccurs="0" />
<xs:element name="ReconcileScriptsMode" type="xs:boolean" minOccurs="0" />
<xs:element name="FolderPath" type="xs:string" minOccurs="0" />
<xs:element name="SelectedFile" type="xs:string" minOccurs="0" />
<xs:element name="UpdateVersionTable" type="xs:boolean" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
<Table1>
<Version>1.2</Version>
<DateUpdated>2004-05-17T10:04:06.9438192+10:00</DateUpdated>
<NewDatabase>true</NewDatabase>
<ConnectionString>Provider=SQLOLEDB;Integrated Security=SSPI;Data Source=(local);Initial Catalog=master</ConnectionString>
<SQLFilePath>ver0001.sql</SQLFilePath>
<TimeOut>5</TimeOut>
<TurnOnMSDE>false</TurnOnMSDE>
<KeepXMLRecords>false</KeepXMLRecords>
<UserMode>true</UserMode>
<ReconcileScriptsMode>true</ReconcileScriptsMode>
<FolderPath>C:\Program Files\SSW SQL Deploy\Samples\DatabaseSQLScripts\</FolderPath>
<SelectedFile />
<UpdateVersionTable>true</UpdateVersionTable>
</Table1>
</NewDataSet>
-
Figure: XML file with version control
The version tags identifies what version the file is. This version should be hard
coded into the application. Every time you change the format of the file, you would
increment this number.
The code below shows how this would be implemented in your project.
-
Public Function XmlValidatore() As Boolean
Dim errors As Boolean = False
Dim fileversion As String = "Not Specified"
Dim msShouldConvertFile As Boolean
Dim sce As XmlSchemaSet = New XmlSchemaSet()
sce.Add("", "\..\NewDataSet.xsd")
Dim documnet As XDocument = XDocument.Load("\..\NewDataSet.xsd")
documnet.Validate(sce, AddressOf XSDErrors)
Dim element As IEnumerable(Of XElement) = _
From el In documnet.Elements() _
Where (el.Name = "version") _
Select el
fileversion = element.ToString()
If fileversion = "" Then
fileversion = "Not specified"
End If
If fileversion = GetXMLFileVersion() Then
If errors Then
Return False
End If
End If
If errors OrElse fileversion <> GetXMLFileVersion() Then
If msShouldConvertFile Then
CovertToCurrentVersion(errors)
End If
Else
Throw New XMLFileVersionException(" File is not in the Latest Version ", GetXMLFileVersion())
End If
Return True
End Function
End Class
Figure: Code to illustrate how to check if the xml file is valid using VB.NET.
Note: to allow backward compatibility, you should give the user an option to convert
old xml files into the new version structure.
-
public bool xmlvalidatore()
{
bool error = false;
string fileversion = "Not Specifide";
bool msShouldConvertFile;
XmlSchemaSet sce = new XmlSchemaSet();
sce.Add(null, @"\..\NewDataSet.xsd");
XDocument document = XDocument.Load(@"\..\NewDataSet.xml");
document.Validate(sce, (o, e) =>
{
error = true;
});
IEnumerable element=
from el in document.Elements()
where el.Name == "version"
select el;
fileversion = element.ToString();
if (fileversion == "")
{
fileversion = "Not Spcified";
}
if (fileversion == GetXMLFileVersion())
{
if (error)
{
return false;
}
}
if (error ||
fileversion != GetXMLFileVersion())
{
if (msShouldConvertFile)
{
ConvertToCurrentVersion(error);
}
}
else
{
throw new XMLFileVersionException("Please Change the file to Latest Version", GetXMLFileVersion());
}
return true;
}
}
}
Figure: Code to illustrate how to check if the xml file is valid using C#.NET.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/VersionXMLFiles.aspx
-
Both controls can represent XML hierarchical data and support Extensible Stylesheet
Language (XSL) templates, which can be used to transform an XML file into a the
correct format and structure. While TreeView can apply Styles more easily, provide
special properties that simplify the customization of the appearance of elements
based on their current state.
-
<asp:TreeView ID="TreeView1" runat="server" DataSourceID="siteMapDataSource" ImageSet="Faq" SkipLinkText ="">
<ParentNodeStyle Font-Bold="False" />
<HoverNodeStyle Font-Underline="True" ForeColor="Purple" />
<SelectedNodeStyle Font-Underline="True" HorizontalPadding="0px" VerticalPadding="0px" />
<NodeStyle Font-Names="Tahoma" Font-Size="8pt" ForeColor="DarkBlue" HorizontalPadding="5px"
NodeSpacing="0px" VerticalPadding="0px" />
</asp:TreeView>
<asp:SiteMapDataSource ID="siteMapDataSource" runat="server" />
-
Figure: Good Code - Use TreeView to represent XML hierarchical data
-
<asp:Xml ID="Xml1" runat="server" DocumentSource="~/Web.xml" TransformSource="~/Style.xsl"></asp:Xml>
-
Figure: Bad Code - Use XML to represent XML document using XSL Transformations
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/TreeViewControl.aspx
-
There are three types of settings files that we may need to use in .NET
- App.Config/Web.Config is the default .NET settings file, including any settings
for the Microsoft Application Blocks (eg. the Exception Management Block and the
Configuration Management Block). These are for settings that don't change from within
the application. In addition, System.Configuration classes don't allow this file to be written to.
- ToolsOptions.Config (an SSW standard) is the file to hold the users own settings,
that are users can change in the Tools - Options.
Eg. ConnectionString, EmailTo, EmailCC
Note: We read and write to this using Microsoft Configuration Application Block.
If we don't use this Block we would store it as a plain XML file and read and write
to it using System.XML classes. The idea is that if something does go wrong when
you are writing to this file, at least the App.Config would not be affected. Also,
this separates our settings (which are few) from the App.Config (which usually has
a lot of stuff that we really dont want a user to stuff around with).
- UserSession.Config (an SSW standard). These are for additional setting files that
the user can not change.
e.g. FormLocation, LastReportSelected
Note: This file is over writable (say during a re-installation) and it will not
affect the user if the file is deleted.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/SettingsInDifferentFiles.aspx
-
Windows Communication Foundation (WCF) extends .NET Framework to enable building
secure, reliable & interoperable Web Services.
WCF demonstrated interoperability with using the Web Services Security (WSS) including
UsernameToken over SSL, UsernameToken for X509 Certificate and X509 Mutual Certificate
profiles.
WSE has been outdated and replaced by WCF and has provided its own set of attributes
that can be plugged into any Web Service application.
- Security
Implementation of security at the message layer security has several policies that
can suite any environment including:
- Windows Token
- UserName Token
- Kerbose Token
- X.509 Certificate Token
At SSW we implement UserName Token using the standard login screen that prompts
for a Username and a Password, which then gets passed into the SOAP header (at message
level) for authorization.
This requires SSL which provides a secure tunnel from client to server.
However, message layer securtiy does not provide authentication security, so it
does not stop the ability for a determined hacker to try user name / password attempts
forever. Custom Policies setup at Application Level can to prevent brute force.
- Performance
Indigo has got the smarts to negotiate to the most performant serialization and
transport protocol that either side of the WS conversation can accommodate, so it
will have the best performance having "all-things-being-equal". You can configure
the web services SSL session simply in the web.config file.
After having Configure an SSL certificate (in the LocalMachine store of the server),
the following lines are required in the web.config:
-
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.serviceModel>
<services>
<service type="WCFService" name="WCFService" behaviorConfiguration="ServiceBehaviour">
<endpoint contract="IWCFService" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IWCFServiceBinding"/>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IWCFServiceBinding" >
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<behavior name="ServiceBehaviour" returnUnknownExceptionsAsFaults="true" >
<serviceCredentials>
<serviceCertificate findValue="CN=SSW"
storeLocation="LocalMachine"
storeName="My" x509FindType="FindBySubjectDistinguishedName"
/>
</serviceCredentials>
</behavior>
</behaviors>
</system.serviceModel>
</configuration>
Figure: Setting the SSL to Web Service for Message Layer Security.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/UsingWCF.aspx
-
Do you let the adapter handle the connection for you?
Did you know if you are using DataSets throughout your application (not data readers)
then you don't need to have any code about connection opening or closing.
Some say it is better to be explicit. However the bottom line is less code is less
bugs.
-
try
{
cnn.Open();
adapter.Fill(dataset);
}
catch (SQLException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
// I'm in the finally block so that I always get called even if the fill fails.
cnn.Close();
}
-
Bad code - The connection code is not needed
-
try
{
adapter.Fill(dataset);
}
catch (SQLException ex)
{
MessageBox.Show(ex.Message);
}
-
Good code - letting the adapter worry about the connection.
Note: A common comment for this rule is...
"Please tell users to explicitly open and close connection - even when the
.NET Framework can do for them"
The developers who prefer the first (more explicit) code example give the following
reasons:
- Explicit Behaviour is always better. Code maintainability. Explicit code is more
understandable than implicit code. Don't make your other developers have to look
up the fact that data adapters automatically maintain the state of your connection
for them.
- Consistency (or a lack of) - not all Framework classes are documented to behave
like this. For example, the IDBCommand.ExecuteNonQuery() will throw an exception
if the connection isn't open (it might be an interface method, but interface exceptions
are documented as a strong guideline for all implementers to follow). The SqlCommand
help doesn't mention anything further about this fact, but considering it's an inherited
class, it would be fair to expect it to behave the same way. A number of the other
methods don't make mention of connection state, making it difficult to know which
basket to put your eggs into...
- Developer Awareness - it's healthy for the developer to be aware that they have
a resource that needs to be handled properly. If they learn that they don't need
to open and close connections here, then when they move onto using other resource
types where this isn't the case then many errors may be produced. For example, when
using file resources, the developer is likely to need to pass and open stream and
needs to remember to close any such streams properly before leaving the function.
- Efficiency (sort of) - In a lot of code it will often populate more than one object
at a time so that if I only open the connection once, execute multiple fills or
commands, then close, then it'll be more clear about what the intent of the developer.
If we left it to the framework, it's likely that the connection will be opened and
closed multiple times; which despite it being really cheap to open out of the connection
pool it will be slightly (itty bitty bit) more efficient but I think the explicit
commands will demonstrate more clearly the intention of the developer.
Bottom line - I wont be swayed - but it is a controversial one. People who agree
with me include:
- Ken Getz
- Paul Sheriff
- Bill Vaughan
- George Doubinski
People who don't:
- Chris Kinsman
- Richard Campbell
- Paul Reynolds
Microsoft's online guide to Improving ADO.NET
performance to see their opinion and other tips.
One final note: This argument is a waste of time.... With code generators developing
most of the Data Access layer of the application, the errors, if any, will be long
gone and the developer is presented with higher level of abstraction that allows
him/her to concentrate on more important things rather than mucking around with
connections. Particularly considering that, when we start using the Provider model
from Whidbey, it won't even be clear whether you're talking to SQL Server
or to an XML file.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/UseAdapterHandleConnection.aspx
-
In situations where you must explicitly open a connection (e.g. when using data
readers), you always:
- open your Connection in a Try Block and
- close it in a Finally statement (So when an Exception is thrown your Connection
can be properly closed)
-
// C#
try
{
cnn.Open();
cmd.ExecuteReader();
}
catch (SQLException ex)
{
MessageBox.Show(ex.Message);
cnn.Close();
}
'VB.NET
Try
cnn.Open()
cmd.ExecuteReader()
Catch (ex as SQLException)
MessageBox.Show(ex.Message)
cnn.Close()
End Try
-
Bad code - Connection is not closed in the finally.
-
// C#
try
{
cnn.Open();
cmd.ExecuteReader();
}
catch (SQLException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
cnn.Close();
}
' VB.NET
Try
cnn.Open()
cmd.ExecuteReader()
Catch (ex as SQLException)
MessageBox.Show(ex.Message)
Finally
cnn.Close()
End Try
-
Good code - The cnn.open is in a Try and the cnn.Close is in a finally
Note:Do not use the 'using' keyword in C#. The using keyword is used to declare
a scope out of which the connection will be disposed. For the sake of consistancy,
we like our VB.NET and C# projects to be as similar as possible. For both languages
you should use a Try..Catch..Finally block.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/TryBlock.aspx
-
Meaningless Catch blocks should never be used because if there is an error in the
application a warning cannot be displayed to the user such as "connection failed".
-
// C#
try
{
cnn.Open();
cmd.ExecuteReader();
}
catch
{ }
finally
{
cnn.Close();
}
End Try
-
Bad code - Meaningless Catch block
-
// C#
try
{
cnn.Open();
cmd.ExecuteReader();
}
catch (SQLException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
cnn.Close();
}
End Try
-
Good code - Meaningful Catch block
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/Catchblocks.aspx
-
Each class definition should live in its own file.
Reasons:
- Easy to locate class definitions outside the Visual Studio IDE (e.g. SourceSafe,
Windows Explorer)
The only exception should be - classes that collectively form one atomic unit of
reuse should live in one file. For example:
-
class MyClass
{
...
}
class MyClassAEventArgs
{
...
}
class MyClassBEventArgs
{
...
}
class MyClassAException
{
...
}
class MyClassBException
{
...
}
-
Bad example - 1 project, 1 file.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/UseOneClassPerFile.aspx
-
There are 5 common methods of inserting rows into your database:
-
Use SqlCommand with an SQL INSERT statement and parameters:
-
public void SQLInsert(string customerID, string companyName, string contactName)
{
SqlConnection sqlcon = new SqlConnection();
sqlcon.ConnectionString = "Persist Security Info=False;Integrated Security=SSPI;database=northwindJV;server=(local);Connect Timeout=5";
SqlCommand sqlcmd = new SqlCommand();
sqlcmd.CommandText = "INSERT Customers(CustomerID, CompanyName, ContactName) VALUES(@CustomerID, @CompanyName, @ContactName)";
sqlcmd.Connection = sqlcon;
sqlcmd.Parameters.Add("@CustomerID", customerID);
sqlcmd.Parameters.Add("@CompanyName", companyName);
sqlcmd.Parameters.Add("@ContactName", contactName);
... // for all columns
try
{
sqlcon.Open();
MessageBox.Show("The number of records updated was: " + sqlcmd.ExecuteNonQuery().ToString());
}
finally
{
sqlcon.Close();
}
}
-
Figure: Inserting rows using INSERT
This approach has two problems - the SQL is inline in the code, and if the database
schema is changed, INSERT statement will have to be manually updated.
-
Use SqlCommand and a stored procedure on the SQL Server:
-
public void SPInsert(string firstName, string surname)
{
SqlConnection sqlcon = new SqlConnection();
sqlcon.ConnectionString = "Persist Security Info=False;Integrated Security=SSPI; database=northwind;server=mySQLServer;Connect Timeout=30";
SqlCommand sqlcmd = new SqlCommand();
sqlcmd.CommandText = "proc_InsertCustomer";
sqlcmd.CommandType = CommandType.StoredProcedure;
sqlcmd.Connection = sqlcon;
sqlcmd.Parameters.Add("@firstName", firstName);
sqlcmd.Parameters.Add("@surname", surname);
... // for all columns
try
{
sqlcon.Open();
sqlcmd.ExecuteNonQuery();
}
finally
{
sqlcon.Close();
}
}
-
Figure: Inserting rows using SqlCommand and a stored procedure on the SQL Server
This method is better because the SQL is not mixed up with the code (it is in a
stored procedure), but it will still break if the database schema is changed, and
the all of the parameters to the stored procedure have to be added manually.
-
Use DataAdapter with SQL INSERT statement, then use DataApdater.Update (strongly-typed-dataset)
-
public void DASQLInsert(string firstName, string surname)
{
SqlConnection sqlcon = new SqlConnection();
sqlcon.ConnectionString = "Persist Security Info=False;Integrated Security=SSPI; database=northwind;server=mySQLServer;Connect Timeout=30";
SqlCommand sqlcmd = new SqlCommand();
sqlcmd.CommandText = "INSERT Customers(firstName, surname) VALUES(@firstName, @surname)";
sqlcmd.Connection = sqlcon;
SqlDataAdapter sqladp = new SqlDataAdapter();
sqladp.InsertCommand = sqlcmd;
NorthWindCustomer dst = new NorthWindCustomer();
NorthWindCustomer.CustomerRow row = dst.Customer.NewCustomerRow();
row.FirstName = firstName;
row.Surname = surname;
dst.Customer.AddCustomerRow(row);
try
{
slqcon.Open();
sqladp.Update(dst);
}
finally
{
sqlcon.Close();
}
}
-
Figure: Inserting rows using DataAdapter with SQL INSERT statement, then use DataApdater.Update
In this example, the SQL is mixed up with the .NET code, and has to be manually
updated if the database schema is changed. However, the strongly typed DataSet automatically
updates when the database schema changes.
-
Use DataAdapter with a stored procedure for INSERT, then use DataAdapter.Update
(strongly-typed-dataset)
-
public void DASPInsert(string firstName, string surname)
{
SqlConnection sqlcon = new SqlConnection();
sqlcon.ConnectionString = "Persist Security Info=False;Integrated Security=SSPI; database=northwind;server=mySQLServer;Connect Timeout=30";
SqlCommand sqlcmd = new SqlCommand();
sqlcmd.CommandText = "proc_InsertCustomer";
sqlcmd.CommandType = CommandType.StoredProcedure;
sqlcmd.Connection = sqlcon;
SqlDataAdapter sqladp = new SqlDataAdapter();
sqladp.InsertCommand = sqlcmd;
NorthWindCustomer dst = new NorthWindCustomer();
NorthWindCustomer.CustomerRow row = dst.Customer.NewCustomerRow();
row.FirstName = firstName;
row.Surname = surname;
dst.Customer.AddCustomerRow(row);
try
{
sqlcon.Open();
sqladp.Update(dst);
}
catch
{
MessageBox.Show(
"Unable to open connection.",
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
sqlcon.Close();
}
}
-
Figure: Inserting rows using DataAdapter with a stored procedure for INSERT, then
use DataAdapter.Update (strongly-typed-dataset) - best for SQL Server
This is the best approach for Microsoft SQL Server. The parameters for the stored
procedure are automatically generated and the strongly typed dataset updates when
the database schema changes.
-
Use DataAdapter with SQL SELECT statement, then use command builder to automatically
create INSERT, UPDATE and DELETE statements as required. Then use DataAdapter.Update
(strongly-typed-dataset).
-
public void DACmdb(string firstName, string surname)
{
SqlConnection sqlcon = new SqlConnection();
sqlcon.ConnectionString = "Persist Security Info=False;Integrated Security=SSPI; database=northwind;server=mySQLServer;Connect Timeout=30";
SqlCommand sqlcmd = new SqlCommand();
sqlcmd.CommandText = "SELECT firstName, surname FROM Customers";
sqlcmd.Connection = sqlcon;
SqlDataAdapter sqladp = new SqlDataAdapter();
sqladp.SelectCommand = sqlcmd;
SqlCommandBuilder cmdb = new SqlCommandBuilder(adp);
NorthWindCustomer dst = new NorthWindCustomer();
NorthWindCustomer.CustomerRow row = dst.Customer.NewCustomerRow();
row.FirstName = firstName;
row.Surname = surname;
dst.Customer.AddCustomerRow(row);
try
{
sqlcon.Open();
sqladp.Update(dst);
}
catch
{
MessageBox.Show(
"Unable to open connection.",
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
sqlcon.Close();
}
}
-
Figure: Inserting rows using DataAdapter with SQL SELECT statement, then use command
builder to automatically create INSERT, UPDATE and DELETE - best for SQL Server
This approach is the best approach for Jet (Access) databases, as stored procedures
in Access are difficult to implement and unreliable. The INSERT statement is automatically
generated by .NET and the strongly typed databases update when the database schema
is changed.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/DataAdapter.aspx
-
Do you put all images in the \images folder?
Instead of images sitting all around the solution, we put all the images in the
same folder.
-
-
Bad example - Images under Product root folder.
-
-
Good example - Images under \Images folder.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/ImagesFolder.aspx
-
Do you keep \images folder image only?
We want to keep clear and simple file structure in our solution. Never put any files
other than images file in \images folder
-
-
Bad example - HTML file in \Images Folder.
-

-
Good example - Images only, clean \Images folder.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/KeepImagesFolderImageOnly.aspx
-
Do you put your setup file in your a \setup folder?
All setup files should stored under setup folder of your project root directory.
-

-
Good example - All the wise setup file in the \setup folder.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/SetupFolder.aspx
-
Do you deploy your applications correctly?
Many applications end up working perfectly on the developer's machine. However once
the application is deployed into a setup package and ready for the public, the application
could suddenly give the user the most horrible experience of his life. There are
plenty of issues that developers don't take into consideration. Amongst the many
issues, three can stand above the rest if the application isn't tested thoroughly:
- The SQL Server Database or the Server machine cannot be accessed by the user, and
so developer settings are completely useless to the user.
- The user doesn't install the application in the default location. (i.e. instead
of C:\Program Files\ApplicationName, the user could install it on D:\Temp\ApplicationName)
- The developer has assumed that certain application dependencies are installed on
the user's machine. (i.e. MDAC; IIS; a particular version of MS Access; or SQL Server
runtime components like sqldmo.dll)
To prevent issues from arising and having to re-deploy continuously which would
only result in embarrassing yourself and the company, there are certain procedures
to follow to make sure you give the user a smooth experience when installing your
application.
- Have scripts that can get the pathname of the .exe that the user has installed the
application on
Wise has a Dialog that prompts the user for the installation directory:
-

Figure: Wise Prompts the user for the installation directory and sets the path to
a property in wise called "INSTALLDIR"
An embedded script must be used if the pathname is necessary in the application
(i.e. like .reg files that set registry keys in registry)
-
'The .reg file includes the following hardcoded lines:
'[HKEY_CLASSES_ROOT\SSWNetToolkit\shell\open\command]
'@="\"C:\\Program Files\\SSW NetToolKit\\WindowsUI\\bin\\SSW.NetToolkit.exe\" /select \"%1\""
'This should be replaced with the following lines:
'[HKEY_CLASSES_ROOT\SSWNetToolkit\shell\open\command]
'@="\"REPLACE_ME\" /select \"%1\""
Dim oFSO, oFile, sFile
Set oFSO = createobject("Scripting.FileSystemObject")
sFile = Property("INSTALLDIR") & "WindowsUI\PartA\UrlAcccess.reg"
Set oFile = oFSO.OpenTextFile(sFile)
regStream = oFile.ReadAll()
oFile.Close
string appPath = replace(Property("INSTALLDIR") & "WindowsUI\bin\SSW.NetToolkit.exe", "\", "\\")
regStream = replace(regStream, "REPLACE_ME", appPath)
Set oFile = oFSO.OpenTextFile(sFile,2)
oFile.Write regStream
oFile.Close
Figure: The "REPLACE_ME" string is replaced with the value of the INSTALLDIR
property in the .reg file
- After setting up the wise file then running the build script, the application must
be first tested on the developers own machine.
Many developers forget to test the application outside the development environment
completely, and don't bother to install the application using the installation package
they have just created.
Doing this will allow them to fix e.g. pathnames of images that might have been
set to a relative path of the running process and not the relative path of the actual
executable.
-
this.pictureReportSample.Image = Image.FromFile(@"Reports\Images\Blank.jpg");
-
Bad code - FromFile() method (as well as Process.Start()) give the relative path
of the running process. This could mean the path relative to the shortcut or the
path relative to the .exe itself, and so an exception will be thrown if the image
cannot be found when running from the shortcut.
-
string appFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location;
string appPath = Path.GetDirectoryName(appFilePath);
this.pictureReportSample.Image = Image.FromFile(appPath + @"\Reports\Images\Blank.jpg");
-
Good code - GetExecutingAssembly().Location will get the pathname of the actual
excecutable and no exception will be thrown.
This exception would never have been found if the developer didn't bother to test
the actual installation package on his own machine.
- Having tested on the developer's machine, the application must be tested on a virtual
machine in a pure environment without dependencies installed in GAC, registry or
anywhere else in the virtual machine.
Users may have MS Access 2000 installed and, the developer's application may behave
differently on an older version of MS Access even though it works perfectly on MS
Access 2003. The most appropriate way of handling this is to use programs like VM
Ware or MS Virtual PC.
This will help the developer test the application on all possible environments to
ensure that it caters for all users, minimizing the amount of assumptions
as possible.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/DeployApplicationsCorrectly.aspx
-
Do you distribute a product in Release mode?
We like to have debugging information in our application, so that we can view the
line number information in the stack trace. However, we won't release our product
in Debug mode, for example if we use "#if Debug" statement in our code we don't
want them to be compiled in the release version. If we want line numbers, we simply
need Debugging Information. You can change an option in the project settings
so these will be generated in when using Release build.
-
#if DEBUG MessageBox.Show("Application started"); #endif
Figure: Code that should only run in Debug mode, we certainly don't want this in the
release version.
-
Figure: Set "Generate Debugging Information" to True on the project
properties page (VS 2003).
-
Figure: Set "Debug Info" to "pdb-only" on the Advanced Build
Settings page (VS 2005).
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/DistributeAProductInReleaseMode.aspx
-
Do you use more meaningful names than Hungarian short form?
Hungarian notation is used in VB6. In .NET, there are over 35,000 classes, so we can't
just call them with three letter short form. We would suggest the developer use the full
class name as in the example below. As a result, the code will be much easier
to read and follow up.
-
//Bad Code
DateTime dt = new DateTime.Now();
DataSet ds = new DataSet();
// It could be confused with Date time.
DataTable dt = ds.Tables[0];
-
Bad code - Without meaningful name.
-
//Good Code
DateTime currentDateTime = new DateTime.Now();
DataSet employmentDataSet = new DataSet();
DataTable ContactDetailsDataTable = ds.Tables[0];
-
Good code - With meaningful name.
More information
on naming convention.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/UseMeaningfulNamesThanHungarian.aspx
-
Do you know how to rename files that under SourceSafe control?
Whenever we rename a file in Visual Studio .NET, the file becomes a new file in
SourceSafe. If the file has been checked-out, the status of old file will remain
as checked-out in SourceSafe.
The step by step to rename a file that under SourceSafe control:
- Save and close the file in Visual Studio .NET, and check in the file if it is checked-out.
- Open Visual SourceSafe Explorer and rename the file.
- Rename it in Visual Studio .NET, click "Continue with change" to the 2 pop-up messages:
-

Figure: Warning message of renaming files under source control.
-

Figure: You are seeing this as the new file name already exists in SourceSafe, just
click "Continue with change".
Visual Studio .NET should find the file under source control and it will come up with
a lock icon.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/RenameSourceSafeFiles.aspx
-
Do you profile your code when optimising performance?
Imagine that you have just had a User Acceptance Test (UAT), and your app has been
reported as being "painfully slow" or "so slow as to be unusable". Now, as a coder,
where do you start to improve the performance? More importantly, do you know how
much your massive changes have improved performance - if at all?
We recommend that you should always use a code profiling tool to measure performance
gains whilst optimising your application. Otherwise, you are just flying blind and
making subjective, unmeasured decisions. Instead, use a tool such as
JetBrains dotTrace profiler. These will guide you as to how to best optimise
any code that is lagging behind the pack. You can run this on both ASP.NET and Windows
Forms Applications. The optimisation process is as follows:
- Profile the application with Jetbrains dotTrace using the "Hot Spot" tab to identify
the slowest areas of your application
-

Figure: Identify which parts of your code take the longest (Hot Spots)
- Some parts of the application will be out of your control e.g. .NET System Classes.
Identify the slowest parts of code that you can actually modify from the Hot Spot
listing
- Determine the cause of the poor performance and optimise (e.g. improve the WHERE
clause or the number of columns returned, reduce the number of loops or use a StringBuilder
instead of string concatenation)
- Re-run the profile to confirm that performance has improved
- Repeat from Step 1 until the application is optimised
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/ProfileYourCodeForPerformance.aspx
-
Do you Add SSW Code Auditor, NUnit and Microsoft FxCop project
files to your Solution
SSW Code Auditor,
NUnit and Microsoft
FxCop are tools to keep your code "healthy". That is why they should be
easily accessible in every solution so that they can be run with a double click
of a mouse button.
-

To add a SSW Code Auditor file to your
solution:
- Start up SSW Code Auditor
- Add a new Job
- Add a the solution file to be scanned
- Select the rules to be run
- Configure email (not required)
- Select File > Save As (into the solution's folder as "codeauditor.SSWCodeAuditor")
- Open your Solution in Visual Studio
- Right click and add existing file
- Select the SSW Code Auditor project file
- Right click the newly added file and select "Open With"
-

- Point it to the SSW Code Auditor executable
See Do you
run SSW Code Auditor?
See Do you
check your code by Code Auditor before check-in?
To add a Microsoft FxCop
file to your solution:
- Stat up Microsoft FxCop
- Create a New Project
- Right click the project and Add Target
- Select the Assembly (DLL/EXE) for the project
- Select File > Save Project As (into the solution's folder as "fxcop.FxCop")
- Open your Solution in Visual Studio
- Right click and add existing file
- Select the Microsoft FxCop project file
- Right click the newly added file and select "Open With"
- Point it to the Microsoft FxCop executable
To add a NUnit
file to your solution:
- Stat up NUnit
- Create a New Project by selecting File > New Project and save it to your
solution directory as "nunit.NUnit"
- From the Project menu select Add Assembly
- Select the Assembly (DLL/EXE) for the project that contains unit tests
- Select File > Save Project
- Open your Solution in Visual Studio
- Right click and add existing file
- Select the NUnit project file
- Right click the newly added file and select "Open With"
- Point it to the NUnit executable
Now you can simply double click these project files to run the corresponding applications.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/AddCAFxCopToSolution.aspx
-
Do you know what files not to put into VSS?
The following files should NOT be included in source safe as they are user specific
files:
- *.scc;*.vspscc - Source Safe Files
- *.pdb - Debug Files
- *.user - User settings for Visual Studio .NET IDE
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/FilesNotToPutIntoVSS.aspx
-
Do you use resource file for storing your static script?
-
StringBuilder sb = new StringBuilder();
sb.AppendLine(@"<script type=""text/javascript"">");
sb.AppendLine(@"function deleteOwnerRow(rowId)");
sb.AppendLine(@"{");
sb.AppendLine(string.Format(@"{0}.Delete({0}.GetRowFromClientId(rowId));", OwnersGrid.ClientID));
sb.AppendLine(@"}");
sb.AppendLine(@"</script>");
-
Bad example - Hard to read ?the string is surrounded by rubbish + inefficient because
you have an object and 6 strings
-
string.Format(@"<script type=""text/javascript"">
function deleteOwnerRow(rowId)
{ {0}.Delete({0}.GetRowFromClientId(rowId)); } </script> ", OwnersGrid.ClientID);
-
Good example Slightly easier to read ?but it is 1 code statement across 10 lines
-
string scriptTemplate = Resources.Scripts.DeleteJavascript;
string script = string.Format(scriptTemplate, OwnersGrid.ClientID);
-
<script type=""text/javascript"">
function deleteOwnerRow(rowId)
{
{0}.Delete({0}.GetRowFromClientId(rowId));
}
</script>
Figure: The code in the first box, the string in the resource file in the 2nd box.
This is the easiest to read + you can localize it eg. If you need to localize an
Alert in the javascript
-
Figure: Add a recource file into your project in VS2005
-
Figure: Read value from the new added resource file
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/UseResourceFileStoringStaticScript.aspx
-
Do you know changes on Datetime in .NET 2.0 and .NET 1.1/1.0
In v1.0 and v1.1 of .NET framework when serializing DateTime values with the XmlSerializer,
the local time zone of machine would always been appended. And when deserializing
on the receiving machine, DateTime values would be automatically adjusted based
on time zone offset relative to the sender time zone. See below example:
-
DataSet returnedResult = webserviceObj.GetByDateCreatedAndEmpID(DateTime.Now,'JZ');
Figure: front end code in .NET v1.1 (front end time zone: GTM+8)
-
[WebMethod] public DataSet GetByDateCreatedAndEmpID(DateTime DateCreated, String
EmpID)
{
EmpTimeDayDataSet ds = new EmpTimeDayDataSet();
m_EmpTimeDayAdapter.FillByDateCreatedAndEmpID(ds, DateCreated.Date, EmpID);
return ds;
}
Figure: web service method (web service server time zone: GTM+10)
When front end calls this web method with the value of current local time (14/01/2006
11:00:00 PM GTM+8) for parameter 'DateCreated', it expects a returned result for
date 14/01/2006, while the service end returns data of 15/01/2006, because 14/01/2006
11:00:00 PM (GTM+8) would be adjusted to be 15/01/2006 01:00:00 AM at the web service
server (GTM+10)
In v1.1/v1.0 you have no way to control this serializing/deserializing behaviour
on DateTime. In v2.0 with the new notion DateTimeKind you can get a workaround for
above example,
-
Datetime unspecifiedTime = DateTime.SpecifyKind(DateTime.Now,DateTimeKind.Unspecified);
DataSet returnedResult = webservceObj.serviceObj.GetByDateCreatedAndEmpID(unspecifiedTime,'JZ');
Figure: front end code in .NET v2.0 (front end time zone: GTM+8)
In this way, the server end will always get a datetime value of 14/01/2006 11:00:00
without GTM offset and return what front end expects
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/ChangesOnDateTime.aspx
-
Do you know how to use Connection String in .NET 2.0?
In .NET 1.1 we used to store our connection string in a configuration file like
this:
-
<configuration>
<appSettings>
<add key="ConnectionString" value ="integrated security=true;data source=(local);initial catalog=Northwind"/>
</appSettings>
</configuration>
and access this connection string in code like this:
-
SqlConnection sqlConn = new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["ConnectionString"]);
-
Bad example - old ASP.NET 1.1 way, untyped and prone to error.
In .NET 2.0 you can access it in another way:
Step 1: Setup your settings in your common project. E.g. Northwind.Common
-

- Figure:
Settings in Project Properties
Step 2: Open up the generated App.config under your common project. E.g. Northwind.Common/App.config
-

- Figure:
Auto generated app.config
Step 3: Copy the content into your entry applications app.config. E.g. Northwind.WindowsUI/App.config
The new setting has been updated to app.config automatically in .NET 2.0
-
<configuration>
<connectionStrings>
<add name="Common.Properties.Settings.NorthwindConnectionString"
connectionString="Data Source=(local);Initial Catalog=Northwind;Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
Then you can access the connection string like this in C#
-
SqlConnection sqlConn = new SqlConnection(Common.Properties.Settings.Default.NorthwindConnectionString);
-
Good example - access our connection string by strongly typed generated settings
class.
Please note these steps does not work for web site model in Visual Studio 2005.
However, they work for other projects such as Windows Form, Console application,
Class Library and Web Application Project.
This is not an issue in a well designed website, since it's connection string will
be defined in the data layer and you can overwrite this connection string
in your web.config.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/ConnectionStringinNET2.aspx
-
Do you avoid using duplicate connection string in web.config?
Since we have many ways to use Connection String in .NET 2.0, it is probable that
we are using duplicate connection string in web.config.
-
<connectionStrings>
<add name="ConnectionString" connectionString="Server=(local);Database=NorthWind;" />
</connectionStrings>
<appSettings>
<add key="ConnectionString" value="Server=(local);Database=NorthWind;"/>
</appSettings>
-
Bad example - use duplicate connection string in web.config.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/DuplicateConnString.aspx
-
Do you use Windows Integrated Authentication connection string in web.config?
Both SQL Server authentication (standard security) and Windows NT authentication
(integrated security) are SQL Server authentication methods that are used to access
a SQL Server database from Active Server Pages (ASP).
We recommend you use the Windows NT authentication by default, because Windows security
services operate by default with the Microsoft Active Directory?directory service,
it is a derivative best practice to authenticate users against Active Directory.
Although you could use other types of identity stores in certain scenarios, for
example Active Directory Application Mode (ADAM) or Microsoft SQL Server? these
are not recommended in general because they offer less flexibility in how you can
perform user authentication.
If not, then add a comment confirming the reason.
-
<connectionStrings>
<add name="ConnectionString" connectionString="Server=(local);Database=NorthWind;Integrated Security=SSPI;" />
</connectionStrings>
-
Bad example - not use Windows Integrated Authentication connection string without
comment.
-
<connectionStrings>
<add name="ConnectionString" connectionString="Server=(local);Database=NorthWind;Integrated Security=SSPI;" />
</connectionStrings>
-
Good example - use Windows Integrated Authentication connection string by default.
-
<connectionStrings>
<add name="ConnectionString" connectionString="Server=(local);Database=NorthWind;uid=sa;pwd=sa;" />
<!--It can't use the Windows Integrated because they are using Novell -->
</connectionStrings>
-
Good example - not use Windows Integrated Authentication connection string with
comment.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/WinIntegratedAuthenticationConnString.aspx
-
Do you highlight strings in your code editor?
It is a good practice to highlight string variables or const in source code editor
of Visual Studio to make them clear. Strings can be easily found especially you
have long source code.
-

-
Default string appearance
-

-
Highlighted string appearance
-

-
Tools | Options form of Visual Studio
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/HlightStrings.aspx
-
Do you use PowerShell to run batch files in Visual Studio?
Windows Command Processor (cmd.exe) cannot run batch files (.bat) in Visual Studio
because it does not take the files as arguments. One way to run batch files in Visual
Studio is to use PowerShell.
-
-
Bad example - Using Windows Command Processor (cmd.exe) for running batch files.
-
-
Good example - Using PowerShell for running batch files.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/PowerShell.aspx
-
Do you make an instruction document at the beginning of a web project and improve it gradually?
An instruction document is very important to maintain a project. With this document,
people new to it can take over it quickly. This document should be created at the
beginning of a project and make sure it's updated gradually.
We recommend that you add this document as a solution item and the name be '_Instructions.doc'
or '_Instructions.docx'.
Here's a summary of what this document would contain. They are not compulsory but
may necessary for running the project.
- Project structure
All parts that composes the project and how they work with each other.
- Third party stuffs
Any software, tools and DLL files that this project uses. (e.g., NHibernate, ComponentArt)
- Database configuration
- Other configurations
- FTP information and Deployment procedure
- Other things to take care of
-
-
Bad example - A project without an instructions.
-
-
Good example - A project with an instructions.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/InstructionsHelpImproveWebProjects.aspx
-
Do you always prefix SQL stored procedure names with the owner
in ADO.NET code?
Stored procedure names in code should always be prefixed with the owner (usually dbo).
This is because if the owner is not specified, SQL Server will look for a procedure
with that name for the currently logged on user first, creating a performance hit.
-
SqlCommand sqlcmd = new SqlCommand(); sqlcmd.CommandText = "
proc_InsertCustomer" sqlcmd.CommandType
= CommandType.StoredProcedure; sqlcmd.Connection = sqlcon;
-
Bad Example
-
SqlCommand sqlcmd = new SqlCommand(); sqlcmd.CommandText = "
dbo.proc_InsertCustomer"; sqlcmd.CommandType
= CommandType.StoredProcedure; sqlcmd.Connection = sqlcon;
-
Good Example
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/PrefixSQLStoredProcedureNames.aspx
-
Do you always make file paths @-quoted?
In C#, backslashes in strings are special characters used to produce "escape
sequences", for example \r\n creates a line break inside the string. This means
that if you want to put a backslash in a string you must escape it out by inserting
two backslashes for every one, e.g. to represent C:\Temp\MyFile.txt you would
use C:\\Temp\\MyFile.txt. This makes the file paths hard to read, and you
can't copy and paste them out of the application.
By inserting an @ character in front of the string, e.g. @"C:\Temp\MyFile.txt",
you can turn off escape sequences, making it behave like VB.NET. File paths should
always be stored like this in strings.
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/MakeFilePathsAtQuoting.aspx
-
Do you always use Option Explicit?
Option Explict should always only be used in VB.NET.
This will turn many of your potential runtime errors into compile time errors, thus
saving you from potential time bombs!
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/UseOptionExplicit.aspx
-
Do you use the Asynchronous and CallBack method when invoking
web methods?
Web service and web invoking becomes more and more popular today as the distributed
systems are widely deployed. However, invoking the normal method may cause a disaster
when applied to web method because transmitting data over Internet may cause your
program to hang for a couple of minutes.
- private static string LoadContentFromWeb(string strUri)
{
...
WebResponse response = request.GetResponse();
...
}
-
Figure: Invoke web method by the normal way (Bad - because this will hang your UI
thread)
The correct way to invoke web method is using asynchronous call to send a request
and use the delegated CallBack method to read the response, see code below:
- public static void GetOnlineVersionAsync(string strUri)
{
try
{
...
IAsyncResult
r = request.BeginGetResponse(new AsyncCallback(ResCallBack), request);
}
catch(WebException ex)
{
Console.WriteLine(ex.ToString()) ;
}
}
private static void ResCallBack(IAsyncResult ar)
{
try
{
string content
= string.Empty;
WebRequest req = (WebRequest)ar.AsyncState;
WebResponse response = req.EndGetResponse(ar);
...
RaiseOnProductUpdateResult(content);
}
catch(WebException ex)
{
Console.WriteLine(ex.ToString());
RaiseOnProductUpdateResult(string.Empty);
}
}
-
Figure: Invoke web method by using asynchronous method and CallBack (Good - UI thread
will be free once the request has been sent)
When working with Web Service, asynchronous methods will be automatically generated
by your web services proxy.

Figure: Automatically generated asynchronous methods
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/AsynchronousMethodandCallBack.aspx
-
Do you use Trace.Fail or set AssertUIEnabled="true" in your web.config?
Have you ever seen dialogs raised on the server-side? These dialogs would hang the thread they were on, and hang IIS until they were dismissed.
In this case, you might use Trace.Fail or set AssertUIEnabled="true" in your web.config.
See Scott's blog Preventing Dialogs on the Server-Side in ASP.NET or Trace.Fail considered Harmful
-
public static void ExceptionFunc(string strException)
{
System.Diagnostics.Trace.Fail(strException);
}
- Figure: Never use Trace.Fail
-
<configuration>
<system.diagnostics>
<assert AssertUIEnabled="true" logfilename="c:\log.txt" />
</system.diagnostics>
</configuration>
- Figure: Never set AssertUIEnabled="true" in web.config
-
<configuration>
<system.diagnostics>
<assert AssertUIEnabled="false" logfilename="c:\log.txt" />
</system.diagnostics>
</configuration>
- Figure: Should set AssertUIEnabled="false" in web.config
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/TraceFailOrSetAssertUIEnabledTrue.aspx
-
Do you make your projects regenerated easily?
If you projects is generated by code generators (Code Smith, RAD Software NextGeneration, etc.), you should make sure it will be regenerated easily.
Code generators can be used to generate whole Windows and Web interfaces, as well as data access layers and frameworks for business layers, making them an excellent time saver. However making the code generators generate your projects for the first time takes much time and involves lots of configurations.
In order to make it easier to do the generation next time, we recommend you putting the command line of operations into a file called "_Regenerate.bat". When you want to generate it next time, just run the bat file and all things are done in a blink.
cs D:\DataDavidBian\Personal\New12345\NetTiers.csp
- Figure: An example of command line of Code Smith for NorthWind
Thus "_Regenerate.bat" file must exist in your projects (of course so must other necessary resources).

- Figure: Good - Have _Regenerate.bat in the solution
*Note: Moved to http://sharepoint.ssw.com.au/Standards/SoftwareDevelopment/RulesToBetterDotNETProjects/Pages/RegenerateStandard.aspx