Skip to content
Snippets Groups Projects
Commit ebc62cbd authored by Lukas Drabek's avatar Lukas Drabek :keyboard:
Browse files

init

parent 74d49136
No related branches found
No related tags found
No related merge requests found
# Dotnet-Guidelines
## Project Structure
When starting a new .NET C# application, the first step is to decide whether it will be a large application or a small, simple CRUD application.
Based on this decision, you can choose the appropriate project structure.
For large applications, it is recommended to use the Clean Architecture pattern. Clean Architecture promotes separation of concerns and maintainability by dividing the application into layers such as Presentation, Application, Domain, and Infrastructure.
On the other hand, for small applications, it is more suitable to use the Vertical Slices pattern. Vertical Slices organize the codebase around features or user stories, making it easier to understand and maintain.
Regardless of the chosen approach, it is important to follow the naming conventions, folder hierarchy, and file organization guidelines established by your organization. This will ensure consistency and ease of collaboration.
## Folder structure
When organizing your .NET projects, it is recommended to group individual files into folders based on features rather than categorizing them by type (e.g., service, interface, etc.). This approach, known as feature-based folder structure, helps to improve maintainability and ease of navigation within the codebase.
In a feature-based folder structure, each folder represents a specific feature or functionality of the application. Within each feature folder, you can further organize the files based on their types, such as services, interfaces, models, etc. This way, related files are kept together, making it easier to locate and work with them.
Here is an example of a feature-based folder structure for both recomanded architecture patterns:
#### Clean Architecture
TODO: screenshot + add link to repo
#### Vertical slices
TODO: screenshot + add link to repo
## Coding Standards
The general rule we follow is "use Visual Studio defaults" to maintain code readability. Additionally, we recommend leveraging LINQ and adopting a more declarative style of coding. LINQ provides a powerful set of operators that allow you to query and manipulate data in a concise and expressive manner. By using LINQ, you can write code that is easier to read and understand, as it focuses on what you want to achieve rather than how to achieve it. Embracing a declarative style of coding encourages you to express your intentions clearly, making your code more maintainable and less prone to bugs.
### C# Coding Style
1. We use [Allman style](http://en.wikipedia.org/wiki/Indent_style#Allman_style) braces, where each brace begins on a new line. A single line statement block can go without braces but the block must be properly indented on its own line and must not be nested in other statement blocks that use braces (See rule 18 for more details). One exception is that a `using` statement is permitted to be nested within another `using` statement by starting on the following line at the same indentation level, even if the nested `using` contains a controlled block.
2. We use four spaces of indentation (no tabs).
3. We use `_camelCase` for internal and private fields and use `readonly` where possible. Prefix internal and private instance fields with `_`, static fields with `s_` and thread static fields with `t_`. When used on static fields, `readonly` should come after `static` (e.g. `static readonly` not `readonly static`). Public fields should be used sparingly and should use PascalCasing with no prefix when used.
4. We avoid `this.` unless absolutely necessary.
5. We always specify the visibility, even if it's the default (e.g.
`private string _foo` not `string _foo`). Visibility should be the first modifier (e.g.
`public abstract` not `abstract public`).
6. Namespace imports should be specified at the top of the file, *outside* of
`namespace` declarations, and should be sorted alphabetically, with the exception of `System.*` namespaces, which are to be placed on top of all others.
7. Avoid more than one empty line at any time. For example, do not have two
blank lines between members of a type.
8. Avoid spurious free spaces.
For example avoid `if (someVar == 0)...`, where the dots mark the spurious free spaces.
Consider enabling "View White Space (Ctrl+R, Ctrl+W)" or "Edit -> Advanced -> View White Space" if using Visual Studio to aid detection.
9. If a file happens to differ in style from these guidelines (e.g. private members are named `m_member`
rather than `_member`), the existing style in that file takes precedence.
10. We only use `var` when the type is explicitly named on the right-hand side, typically due to either `new` or an explicit cast, e.g. `var stream = new FileStream(...)` not `var stream = OpenStandardInput()`.
- Similarly, target-typed `new()` can only be used when the type is explicitly named on the left-hand side, in a variable definition statement or a field definition statement. e.g. `FileStream stream = new(...);`, but not `stream = new(...);` (where the type was specified on a previous line).
11. We use language keywords instead of BCL types (e.g. `int, string, float` instead of `Int32, String, Single`, etc) for both type references as well as method calls (e.g. `int.Parse` instead of `Int32.Parse`). See issue [#13976](https://github.com/dotnet/runtime/issues/13976) for examples.
12. We use PascalCasing to name all our constant local variables and fields. The only exception is for interop code where the constant value should exactly match the name and value of the code you are calling via interop.
13. We use PascalCasing for all method names, including local functions.
14. We use ```nameof(...)``` instead of ```"..."``` whenever possible and relevant.
15. Fields should be specified at the top within type declarations.
16. When including non-ASCII characters in the source code use Unicode escape sequences (\uXXXX) instead of literal characters. Literal non-ASCII characters occasionally get garbled by a tool or editor.
17. When using labels (for goto), indent the label one less than the current indentation.
18. When using a single-statement if, we follow these conventions:
- Never use single-line form (for example: `if (source == null) throw new ArgumentNullException("source");`)
- Using braces is always accepted, and required if any block of an `if`/`else if`/.../`else` compound statement uses braces or if a single statement body spans multiple lines.
- Braces may be omitted only if the body of *every* block associated with an `if`/`else if`/.../`else` compound statement is placed on a single line.
19. Make all internal and private types static or sealed unless derivation from them is required. As with any implementation detail, they can be changed if/when derivation is required in the future.
An [EditorConfig](https://editorconfig.org "EditorConfig homepage") file (`.editorconfig`) has been provided at the root of the runtime repository, enabling C# auto-formatting conforming to the above guidelines.
We also use the [dotnet-format tool](https://learn.microsoft.com/dotnet/core/tools/dotnet-format) to ensure the code base maintains a consistent style over time, the tool automatically fixes the code base to conform to the guidelines outlined above.
#### Example File:
``ObservableLinkedList`1.cs:``
```C#
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using Microsoft.Win32;
namespace System.Collections.Generic
{
public partial class ObservableLinkedList<T> : INotifyCollectionChanged, INotifyPropertyChanged
{
private ObservableLinkedListNode<T> _head;
private int _count;
public ObservableLinkedList(IEnumerable<T> items)
{
if (items == null)
throw new ArgumentNullException(nameof(items));
foreach (T item in items)
{
AddLast(item);
}
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public int Count
{
get { return _count; }
}
public ObservableLinkedListNode AddLast(T value)
{
var newNode = new LinkedListNode<T>(this, value);
InsertNodeBefore(_head, node);
}
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler handler = CollectionChanged;
if (handler != null)
{
handler(this, e);
}
}
private void InsertNodeBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)
{
...
}
...
}
}
```
``ObservableLinkedList`1.ObservableLinkedListNode.cs:``
```C#
using System;
namespace System.Collections.Generics
{
partial class ObservableLinkedList<T>
{
public class ObservableLinkedListNode
{
private readonly ObservableLinkedList<T> _parent;
private readonly T _value;
internal ObservableLinkedListNode(ObservableLinkedList<T> parent, T value)
{
Debug.Assert(parent != null);
_parent = parent;
_value = value;
}
public T Value
{
get { return _value; }
}
}
...
}
}
```
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
#### When to use internals vs. public and when to use InternalsVisibleTo
Use InternalsVisibleTo when sharing code between types in the same assembly, same feature area, or to unit test internal types and members. If two runtime assemblies need to share common helpers then we will use a "shared source" solution with build-time only packages. Check out the some of the projects in https://github.com/aspnet/Common/ and how they are referenced from other solutions.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
If two runtime assemblies need to call each other's APIs, consider making the APIs public or if there's enough extensibility for a customer to perform something similar. If we need it, it is likely that our customers need it.
## Add your files
#### Async method patterns
By default all async methods must have the Async suffix. There are some exceptional circumstances where a method name from a previous framework will be grandfathered in.
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
Passing cancellation tokens is done with an optional parameter with a value of default(CancellationToken), which is equivalent to CancellationToken.None (one of the few places that we use optional parameters). The main exception to this is in web scenarios where there is already an HttpContext being passed around, in which case the context has its own cancellation token that can be used when needed.
Sample async method:
```C#
public Task GetDataAsync(
QueryParams query,
int maxData,
CancellationToken cancellationToken = default(CancellationToken))
{
...
}
```
cd existing_repo
git remote add origin https://code.it4i.cz/dra0060/dotnet-guidelines.git
git branch -M main
git push -uf origin main
#### Extension method patterns
The general rule is: if a regular static method would suffice, avoid extension methods.
Extension methods are often useful to create chainable method calls, for example, when constructing complex objects, or creating queries.
Internal extension methods are allowed, but bear in mind the previous guideline: ask yourself if an extension method is truly the most appropriate pattern.
The namespace of the extension method class should generally be the namespace that represents the functionality of the extension method, as opposed to the namespace of the target type. One common exception to this is that the namespace for middleware extension methods are normally always the same as the namespace of IAppBuilder.
The class name of an extension method container (also known as a "sponsor type") should generally follow the pattern of <Feature>Extensions, <Target><Feature>Extensions, or <Feature><Target>Extensions. For example:
```C#
namespace Food
{
class Fruit { ... }
}
namespace Fruit.Eating
{
class FruitExtensions
{
public static void Eat(this Fruit fruit);
}
OR
class FruitEatingExtensions
{
public static void Eat(this Fruit fruit);
}
OR
class EatingFruitExtensions
{
public static void Eat(this Fruit fruit);
}
}
```
## Integrate with your tools
When writing extension methods for an interface the sponsor type name must not start with an I.
#### Ternary expressions
```c#
// single expression - single line
var result = booleanExpression ? resultA : resultB;
- [ ] [Set up project integrations](https://code.it4i.cz/dra0060/dotnet-guidelines/-/settings/integrations)
// single expression - multi line
var result = booleanExpression
? resultA
: resultB;
// multiple expressions
var result = booleanExpressionA ? resultA
: booleanExpressionB ? resultB
: booleanExpressionC ? resultC
: resultD;
```
```c#
Lambda expressions
// single line
var result = data.Where(x => booleanExpression).ToList();
// multi line
var result = data
.Where(x => booleanExpression)
.OrderByDescending(x => x.DateTime)
.FirstOrDefault();
// nested expressions
var result = data
.Where(x => booleanExpressionA)
.Select(x => x.ListA
.Where(y => booleanExpressionB)
.OrderByDescending(y => y.DateTime)
.FirstOrDefault())
.ToList();
```
### Unit tests and functional tests
#### Assembly naming
The unit tests for the Microsoft.Fruit assembly live in the Microsoft.Fruit.Tests assembly.
The functional tests for the Microsoft.Fruit assembly live in the Microsoft.Fruit.FunctionalTests assembly.
In general there should be exactly one unit test assembly for each product runtime assembly. In general there should be one functional test assembly per repo. Exceptions can be made for both.
#### Unit test class naming
Test class names end with Test and live in the same namespace as the class being tested. For example, the unit tests for the Microsoft.Fruit.Banana class would be in a Microsoft.Fruit.BananaTest class in the test assembly.
#### Unit test method naming
Unit test method names must be descriptive about what is being tested, under what conditions, and what the expectations are. Pascal casing and underscores can be used to improve readability. The following test names are correct:
```
PublicApiArgumentsShouldHaveNotNullAnnotation
Public_api_arguments_should_have_not_null_annotation
```
The following test names are incorrect:
```
Test1
Constructor
FormatString
GetData
```
#### Unit test structure
The contents of every unit test should be split into three distinct stages, optionally separated by these comments:
```
// Arrange
// Act
// Assert
```
The crucial thing here is that the Act stage is exactly one statement. That one statement is nothing more than a call to the one method that you are trying to test. Keeping that one statement as simple as possible is also very important. For example, this is not ideal:
```C#
int result = myObj.CallSomeMethod(GetComplexParam1(), GetComplexParam2(), GetComplexParam3());
```
This style is not recommended because way too many things can go wrong in this one statement. All the GetComplexParamN() calls can throw for a variety of reasons unrelated to the test itself. It is thus unclear to someone running into a problem why the failure occurred.
## Collaborate with your team
The ideal pattern is to move the complex parameter building into the Arrange section:
```C#
// Arrange
P1 p1 = GetComplexParam1();
P2 p2 = GetComplexParam2();
P3 p3 = GetComplexParam3();
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
// Act
int result = myObj.CallSomeMethod(p1, p2, p3);
## Test and Deploy
// Assert
Assert.AreEqual(1234, result);
```
Now the only reason the line with CallSomeMethod() can fail is if the method itself blew up. This is especially important when you're using helpers such as ExceptionHelper, where the delegate you pass into it must fail for exactly one reason.
#### Testing exception messages
In general testing the specific exception message in a unit test is important. This ensures that the exact desired exception is what is being tested rather than a different exception of the same type. In order to verify the exact exception it is important to verify the message.
To make writing unit tests easier it is recommended to compare the error message to the RESX resource. However, comparing against a string literal is also permitted.
```C#
var ex = Assert.Throws<InvalidOperationException>(
() => fruitBasket.GetBananaById(1234));
Assert.Equal(
Strings.FormatInvalidBananaID(1234),
ex.Message);
```
Use xUnit.net's plethora of built-in assertions
xUnit.net includes many kinds of assertions – please use the most appropriate one for your test. This will make the tests a lot more readable and also allow the test runner report the best possible errors (whether it's local or the CI machine). For example, these are bad:
```C#
Assert.Equal(true, someBool);
Assert.True("abc123" == someString);
Assert.True(list1.Length == list2.Length);
for (int i = 0; i < list1.Length; i++)
{
Assert.True(
String.Equals
list1[i],
list2[i],
StringComparison.OrdinalIgnoreCase));
}
```
These are good:
```C#
Assert.True(someBool);
Use the built-in continuous integration in GitLab.
Assert.Equal("abc123", someString);
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
// built-in collection assertions!
Assert.Equal(list1, list2, StringComparer.OrdinalIgnoreCase);
```
#### Parallel tests
By default all unit test assemblies should run in parallel mode, which is the default. Unit tests shouldn't depend on any shared state, and so should generally be runnable in parallel. If the tests fail in parallel, the first thing to do is to figure out why; do not just disable parallel tests!
***
For functional tests it is reasonable to disable parallel tests.
# Editing this README
#### Use only complete words or common/standard abbreviations in public APIs
Public namespaces, type names, member names, and parameter names must use complete words or common/standard abbreviations.
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
These are correct:
```C#
public void AddReference(AssemblyReference reference);
public EcmaScriptObject SomeObject { get; }
```
These are incorrect:
```C#
public void AddRef(AssemblyReference ref);
public EcmaScriptObject SomeObj { get; }
```
## Suggestions for a good README
## Common Patterns
This section contains common patterns used in our code.
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
### Logging patterns
1. Always specify an EventId. Include a numeric ID and a name. The name should be a PascalCasedCompoundWord (i.e. no spaces, and each "word" within the name starts with a capital letter).
## Name
Choose a self-explaining name for your project.
2. In production code, use "pre-compiled logging functions" (see below). Test code can use any kind of logging
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
3. Prefer defining pre-compiled messages in a static class named Log that is a nested class within the class you are logging from. Messages that are used by multiple components can be defined in a shared class (but this is discouraged).
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
4. Consider separating the Log nested class into a separate file by using partial classes. Name the additional file [OriginalClassName].Log.cs
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
5. Never use string-interpolation ($"Foo {bar}") for log messages. Log message templates are designed so that structured logging systems can make individual parameters queryable and string-interpolation breaks this.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
6. Always use PascalCasedCompoundWords as template replacement tokens.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
#### Pre-compiled Logging Functions
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
Production code should use "pre-compiled" logging functions. This means using LoggerMessage.Define to create a compiled function that can be used to perform logging. For example, consider the following log statement:
```C#
public class MyComponent
{
public void MyMethod()
{
_logger.LogError(someException, new EventId(1, "ABadProblem"), "You are having a bad problem and will not go to {Location} today", "Space");
}
}
```
The logging infrastructure has to parse the template ("You are having a bad problem and will not go to {Location} today") every time the log is written. A pre-compiled logging function allows you to compile the template once and get back a delegate that can be invoked to log the message without requiring the template be re-parsed. For example:
```C#
public class MyComponent
{
public void MyMethod()
{
Log.ABadProblem(_logger, "Space", someException);
}
private static class Log
{
private static readonly Action<ILogger, string, Exception> _aBadProblem =
LoggerMessage.Define<string>(
LogLevel.Error,
new EventId(2, "ABadProblem"),
"You are having a bad problem and will not go to {Location} today");
public static void ABadProblem(ILogger logger, string location, Exception ex) => _aBadProblem(logger, location, ex);
}
}
```
If MyComponent is a large class, consider splitting the Log nested class into a separate file:
MyComponent.cs
```c#
public partial class MyComponent
{
public void MyMethod()
{
Log.ABadProblem(_logger, "Space", someException);
}
}
```
MyComponent.Log.cs
```c#
public partial class MyComponent
{
private static class Log
{
private static readonly Action<ILogger, string, Exception> _aBadProblem =
LoggerMessage.Define<string>(
LogLevel.Error,
new EventId(2, "ABadProblem"),
"You are having a bad problem and will not go to {Location} today");
public static void ABadProblem(ILogger logger, string location, Exception ex) => _aBadProblem(logger, location, ex);
}
}
```
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## ss
Version Control: Specify guidelines for using version control systems like Git. Define how to structure branches (e.g., feature branches, bug fixes, main branches) and how to manage pull requests and code reviews to ensure code quality.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
Testing Standards: Encourage unit testing and integration testing as integral parts of the development process. Recommend frameworks like xUnit or NUnit and set standards for writing test cases, structuring test projects, and ensuring sufficient test coverage.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
Dependency Management: Define how to manage dependencies and package versions effectively. Encourage the use of tools like NuGet and establish guidelines for updating packages and handling version conflicts.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
Continuous Integration/Continuous Deployment (CI/CD): Outline a CI/CD pipeline setup, including automated testing, building, and deployment processes. Specify preferred tools and configurations, like Azure DevOps or GitHub Actions.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
Security Best Practices: Highlight security considerations specific to .NET development, such as secure coding practices, handling sensitive information, and preventing common vulnerabilities like SQL injection or cross-site scripting (XSS).
## License
For open source projects, say how it is licensed.
Documentation: Stress the importance of documentation throughout the project lifecycle. Recommend tools like XML comments for documenting code and README files for project-level documentation.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
Learning Resources: Provide a list of resources for new employees to continue learning and stay updated on .NET 8 developments. This can include official documentation, online courses, or relevant blogs and forums.
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment