Make sure that your environment path
has only one reference to typescript and that it's the last one.
To verify that, run tsc --version
and check if you have the latest one.
The latest typescript version is located here C:\Program Files (x86)\Microsoft SDKs\TypeScript\1.7
(or above).
Also make sure that the file \BugTracker.App\BugTracker.App.csproj
's xml property TypeScriptToolsVersion
also reffers to the same version.
Sadly there is no support yet to do all that in visual studio.
cd BugTracker.App
npm run start-dev
- Register a new user
- Url: POST
http://localhost:16449/api/User/register
- Data:
{ "Username": "Bob" }
- Response:
{ "Id": "e9d41068-dec5-42fa-9089-7b42f3dc50dd", "Name": "Bob" }
- Url: POST
- Read an user
- Url: GET
http://localhost:16449/api/User/get/?id={userid}
- Response:
{ "Id": "e9d41068-dec5-42fa-9089-7b42f3dc50dd", "Name": "Bob" }
- Url: GET
- Create an issue for an user
- Url: POST
http://localhost:16449/api/Issue/create
- Data:
{ "UserId": "e9d41068-dec5-42fa-9089-7b42f3dc50dd", "Title": "the title", "Content": "the content" }
- Response:
{ "Id": "ea73b9b7-233a-4212-8432-5d7a68335cac", "UserId": "e9d41068-dec5-42fa-9089-7b42f3dc50dd", "Title": "the title", "Content": "the content", "ReportDate": "2016-01-20T11:53:02.2886864Z", "IsClosed": false }
- Url: POST
- Get all issues reported by a user
- Url: GET
http://localhost:16449/api/Issue/getallbyuser/?userid={userid}
- Response:
[{ "Id": "ea73b9b7-233a-4212-8432-5d7a68335cac", "UserId": "e9d41068-dec5-42fa-9089-7b42f3dc50dd", "Title": "the title", "Content": "the content", "ReportDate": "2016-01-20T11:53:02.2886864Z", "IsClosed": false }]
- Url: GET
- Validation of input data (everything that enters the application) must happen right at the application's border (eg: ASP.net, WebAPI, Database, 3rd party services, etc). After that, data is considered as safe to use which means no
data == null
,userId <= -1
or other plausibility checks are required. Read Appendix A - Why is evil? for explanation. - Never return null from a method. If something cannot be found, throw an exception!
- Use the try-get-pattern (eg.
bool TryGetByXYZ(int, out object)
orbool TryParse(string, out object)
) instead a null-checks on return values. (For theasync
version see theasync
section) - Access modifier must be as restrictive as possible.
using
-statements must be placed at the start of the file, outside of the namespace declaration- Multiple classes per file are not allowed (except non-generic and generic implementations of the same type).
- Using
#regions
is prohibited - Names of methods should expose their intent
- Documentation (if any) must contain why something happen, not how.
- Base classes must be postfixed with
Base
. - Mutable class properties must be exposed as
Properties
, not asFields
. - Prefer the use of List for public api's instead of IEnumerable to signal that the collection is materialized. Otherwise the consumer of your public methods can get confused and call
.ToList()
on your collections. - Avoid throwing the base exception type
Exception
. Instead, find or create appropiate exception types likeNotSupportedExpection
orInvalidOperationException
. - Avoid ANY static properties or methods!
- Please avoid returning method returns like
return GetSomeobject(...)
or passing method returns as parameters likecollection.AddRange(GetMassiveArray(...))
. Instead, assign the output to a variable and return this one instead. Exceptions to this rule are:return returnValue.ToTaskResult();
andreturn ex.ToExceptionTaskResult();
. - Use commands to manipulate data in the database so that it's easily testable and reusable.
-
Name an assembly or namespace singular or plural?
- If it's related to a topic: singular
- eg. BugTracker.Database
- If it's a collection of the "same" type: plural
- eg. BugTracker.Database.Entities
- If it's related to a topic: singular
-
Assemblies
- Place your project in the appropiate folder (applications, libraries, etc.) and name the folder as your assembly name except the prefix "BugTracker.".
- The project file itself has to be named exactly as the assembly name, eg. BugTracker.SomeLibrary.
-
Assembly postfixes
.Tests
for unit tests.Models
for collection of shared POCO models (not database entities).Database
for database projects that contains the db context and the entities as a subfolder.WebAPI
for web api projects.WinService
for windows services.Shared
contains only extensions to the BugTracker.Shared framework (usually not needed at all!).WinApp
for winform applications.WpfApp
for wpf applications.WebApp
for pure html/js/css projects that run on the client (does not contains api projects, databse, ...)
We use the async
and await
whenever possible to keep the application "fluid".
The class ObjectExtensions
contains useful methods to wrap objects with task-results or with Maybe
.
That means you can use someObject.ToTaskResult()
instead Task.FromResult(someObject)
, same with ToMaybe
.
Once you use async in your method signature you're not allowed to use ref
or out
anymore.
Since we never ever return null, we have to use a Nullable<T>
-alike construct to mimic the same for classes.
Task<Maybe<User>> TryGetByNameAsync(string name);
Notice the Try
method name prefix to indicate, that we return a Maybe<T>
here.
Sometimes you have to implement or override methods that return a Task.
If you have async
in your method signature but you don't have an awaitable code in your methods you will get the following error:
This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
This error has it's raison d'ĂŞtre (reason for existance) because a thoughtless usage of async
signals the reader that he has to lookout for awaitable code, which is not the case if the warning shows up.
Please use the following components in your synchronious methods:
For Task
(non-generic) return
public Task Run()
{
// do something
return Task.CompletedTask;
}
For Task<T>
return
public Task<int> Run()
{
// do something
int result = 1 + 2;
return result.ToTaskResult();
}
For exception handling
try
{
return returnValue.ToTaskResult();
}
catch (Exception ex)
{
return ex.ToExceptionTaskResult();
// --OR--
return ex.ToExceptionTaskResult<TReturnType>();
}
You need to create a public class, inheriting from BugTracker.Shared.Infrastructure.IDependencyModule
and place it at ProjectNamespace.DependencyModules
.
There you get access to the unityContainer to register types, instances or factories.
You will also get the infromation which kind of registration is required (eg. Normal, Web). This is necessary to set the lifetime manager (eg. PerThread, PerRequest, etc.).
Omit named registrations unless you have a really good reason to not to do so.
Always specify the LifetimeManager even it's the default one.
public class CommandDepencencyModule : IDependencyModule
{
public void Register(IUnityContainer unityContainer, RegistrationMode mode)
{
unityContainer.RegisterType<IInterfaceName, ConcreteImplementation>(new ContainerControlledLifetimeManager());
// ...
}
}
To gain access to components use only the Constructor Injection.
public DataTable(ILog logger, IUserAccess userAccess)
{
...
}
The component Check
is used to unify validation code. Please extend this component with necessary assertions instead duplicating validation code.
It's capable to signal mismatches to ReSharper via the Code Annotation. Please note that you DO NOT need to validate dependency injection parameters since unity will throw an exception if it can't find a requested dependency.
We have static classes with static constants to avoid the massive use of magic strings.
Please add all your lexical identifiers into these classes. Have a look at Constants
for more information.
The command pattern allows you to pack various steps for a certain task into a single place. It could be seen as a compilation of validation steps, calls to repository methods to the database context and logic, wrapped with try/catch to reduce error checking and logging in case of an error. Notice that a command without return value (eg. DeleteUser) has to inherit from CommandBase instead of the generic version.
A typical command looks like that:
public class CreateUserCommand : CommandBase<User>
{
private readonly ILog logger;
private readonly IUserAccess userAccess;
private UserModel userModelToSave;
public CreateUserCommand(ILog logger, IUserAccess userAccess)
{
this.logger = logger;
this.userAccess = userAccess;
}
public void InitializeWithUserModel(UserModel userModel)
{
userModelToSave = userModel;
}
protected override async Task<CanExecuteCommandResult> CanExecuteAsync()
{
if (userModelToSave == null)
{
return CanExecuteCommandResult.Error("UserModel is null.");
}
if (userModelToSave.Name == null)
{
return CanExecuteCommandResult.Error("Name is null.");
}
var userExists = await userAccess.ExistsByNameAsync(userModelToSave.Name);
if (userExists)
{
return CanExecuteCommandResult.Error($"User with name '{userModelToSave.Name}' already exists.");
}
return await base.CanExecuteAsync();
}
protected override Task<CommandResult<User>> ExecuteAsync()
{
var user = new User { Name = userModelToSave.Name };
logger.Debug("Adding user to database");
userAccess.AddNewUserAsync(user);
var commandResult = CommandResult<User>.FromSuccess(user);
return commandResult.ToTaskResult();
}
}
The methods of a command you can define are:
- Constructor
- Define here your dependencies which get injected later on
InitializeWithUserModel(UserModel)
(or whatever name will be appropiate)- A method that will be used by the
CommandRepository
to pass additional parameters to the command. You can have more than one if you plan to deal with more than one business case in your command. Please pay attention that this methods are not guarded and may throw erros if you're not careful.
- A method that will be used by the
CanExecuteAsync
- Put your validation logic here and return violations via
CanExecuteCommandResult.Error
.
- Put your validation logic here and return violations via
ExecuteAsync
- Put your execution logic here. Please remember that you don't have to deal with database savings.
needs dependency injection registration
The command repository is a collection of methods that create commands,
initialize them and return them in form of CommandBase
or CommandBase<ReturnType>
.
A typical command repository looks loke that:
public class CommandRepository
{
private readonly ICommandFactory commandFactory;
public CommandRepository(ICommandFactory commandFactory)
{
this.commandFactory = commandFactory;
}
public CommandBase<User> CreateUser(UserModel userModel)
{
CreateUserCommand command = commandFactory.CreateCommand<CreateUserCommand, User>();
command.InitializeWithUserModel(userModel);
return command;
}
}
This component is allowed to execute a command.
It coordinates the call to initialization, validation and execution
and returns a CommandResult or CommandResult<TResult>
which indicates if
the execution was successful or in turn contains the error.
To obtain a logger you need to depend on ILog
located in the Common.Logging NuGet package.
The type of the logger is determined while building up the dependency graph so you don't have
to set any type arguments for the logger.
The Bootstrapper is used to start up the essential components of the infrastructure like Unity for Dependency Injection.
The CommandFactory is used to create commands in the CommandRepository and logs errors in case of initialization problems. Never bypass this component to create commands.
namespace <AssemblyUnderTest>.Tests.<NamespaceOfComponentUnderTest>
{
[TestFixture]
public class <ComponentUnderTest>Tests : TestBase
{
[Test]
public async void <MethodName>_<Input/Behaviour>_<ExpectedResult>()
{
// arrange
<Test setup goes here>
// act
<A single line that we really want to test>
// assert
<Assertions goes here>
}
}
}
Always add the arrange
, act
and assert
sections so that a reader of the test can easily identify what the test is doing.
Please make sure that you only have a single line in the act
section so that we know in which state the test is broken.
The TestBase contains important components that you usually would obtain via Dependency Injection like a Logger or the CommandExecutor. Please inherit from this base class to gain access to this elements instead creating them yourself.
We don't use Dependency Injection in tests, therefore you have to mock every object that you need yourself or (in common situations) provide it via a derivated class from TestBase.
Sometimes it's handy to use the original implementations. In such cases, set your implementations to be visible to the test framework:
[assembly: InternalsVisibleTo("BugTracker.TestUtils")]
To create a mock use the TestBase.CreateMock
or TestBase.CreateMockAndRegisterInDI
method.
Creating the mock with the CreateMock*
methods ensure that the mocks are verified (check if all setups are used) after the tests.
Also, always create the mocks with behavior MockBehavior.Strict
to ensure that the mock is only doing what you have setup before.
// arrange
var userAccessMock = CreateMock<IUserAccess>();
userAccessMock.Setup(...);
// act
// ...
// assert
// ...
Models from C# get generated from Typewriter in the \BugTracker.App\Static\app\models\generated\GernateServerModels.tst
file.
To add new models you need to add a C# file to BugTracker.App.Models
which will be converted to \BugTracker.App\Static\app\models\generated\*.ts
.
Note that only public properties are taken into account.
Additional to that you have to extend the \BugTracker.App\Static\app\models\models.ts
file with the new model (there is no automatism yet).
- normal property: no special api required
- key property: annotate all key properties with
[Key]
to sign generate correct remove methods on referee models - lists: use
List<T>
as list format. annotate the porperty withTypescriptListType
and choose the correct type from the enum.
Per default, log4net does not log exception data into the logfiles. This can be problematic when it comes to command results and other exceptions that contains such data (eg. current user id, etc). To get this information into the logs, we need to add a custom log4net renderer like that:
<log4net>
...
<renderer renderingClass="BugTracker.Shared.Logging.ExceptionObjectLogger, BugTracker.Shared" renderedClass="System.Exception" />
...
</log4net>
This will append the exception data at the end of the log message up to a depth of 10 levels.
2015-10-27 16:18:09,962 [Runner thread] DEBUG BugTracker.DependencyInjectionTest.Tests.Commands.CreateUserCommandTest [(null)] - !!
NUnit.Framework.AssertionException ---> NUnit.Framework.AssertionException
--- End of inner exception stack trace ---
Exception data on level 0 (1 items):
[asd]: 123
Please change this document if you feel it's necessary, contains any errors or need an extension.
(source)
There are several problems with using null references in code.
- First, it's generally used to indicate a special state. Rather than defining a new class or constant for each state as specializations are normally done, using a null reference is using a lossy, massively generalized type/value.
- Second, debugging code becomes more difficult when a null reference appears and you attempt to determine what generated it, which state is in effect and its cause even if you can trace its upstream execution path.
- Third, null references introduces additional code paths to test.
- Fourth, once null references are used as valid states for parameters as well as return values, defensive programming (for states caused by design) requires more null reference checking to be done in various places ... just in case.
- Fifth, the language's runtime is already performing type checks when it performs selector lookup on an object's method table. So you're duplicating effort by checking if the object's type is valid/invalid and then having the runtime check the valid object's type to invoke its method.
Why not use the NullObject pattern to take advantage of the runtime's check to have it invoke NOP methods specific to that state (conforming to the regular state's interface) while also eliminating all the extra checking for null references throughout your codebase?
- It involves more work by creating a NullObject class for each interface with which you want to represent a special state. But at least the specialization is isolated to each special state, rather than the code in which the state might be present. IOW, the number of tests are reduced because you have fewer alternate execution paths in your methods.