mirror of
https://github.com/m-lamonaca/dev-notes.git
synced 2025-06-09 03:07:13 +00:00
Merge branch 'main' into dotnet/net-7
This commit is contained in:
commit
77ec748785
117 changed files with 0 additions and 1260 deletions
2925
dotnet/C#/C#.md
Normal file
2925
dotnet/C#/C#.md
Normal file
File diff suppressed because it is too large
Load diff
195
dotnet/C#/async-programming.md
Normal file
195
dotnet/C#/async-programming.md
Normal file
|
@ -0,0 +1,195 @@
|
|||
# [Async Programming](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/)
|
||||
|
||||
## Task Asynchronous Programming Model ([TAP][tap_docs])
|
||||
|
||||
[tap_docs]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model
|
||||
|
||||
It's possible to avoid performance bottlenecks and enhance the overall responsiveness of an application by using asynchronous programming.
|
||||
However, traditional techniques for writing asynchronous applications can be complicated, making them difficult to write, debug, and maintain.
|
||||
|
||||
C# 5 introduced a simplified approach, **async programming**, that leverages asynchronous support in the .NET Runtime.
|
||||
The compiler does the difficult work that the developer used to do, and the application retains a logical structure that resembles synchronous code.
|
||||
|
||||
In performance-sensitive code, asynchronous APIs are useful, because instead of wasting resources by forcing a thread to sit and wait for I/O to complete, a thread can kick off the work and then do something else productive in the meantime.
|
||||
|
||||
The `async` and `await` keywords in C# are the heart of async programming.
|
||||
|
||||
```cs
|
||||
public async Task<TResult> MethodAsync
|
||||
{
|
||||
Task<TResult> resultTask = obj.OtherMethodAsync();
|
||||
|
||||
DoIndependentWork();
|
||||
|
||||
TResult result = await resultTask;
|
||||
|
||||
// if the is no work to be done before awaiting
|
||||
TResult result = await obj.OtherMethodAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
Characteristics of Async Methods:
|
||||
|
||||
- The method signature includes an `async` modifier.
|
||||
- The name of an async method, by convention, ends with an "Async" suffix.
|
||||
- The return type is one of the following types:
|
||||
- `Task<TResult>` if the method has a return statement in which the operand has type `TResult`.
|
||||
- `Task` if the method has no return statement or has a return statement with no operand.
|
||||
- `void` if it's an async event handler.
|
||||
- Any other type that has a `GetAwaiter` method (starting with C# 7.0).
|
||||
- Starting with C# 8.0, `IAsyncEnumerable<T>`, for an async method that returns an async stream.
|
||||
|
||||
The method usually includes at least one `await` expression, which marks a point where the method can't continue until the awaited asynchronous operation is complete.
|
||||
In the meantime, the method is suspended, and control returns to the method's caller.
|
||||
|
||||
### Threads
|
||||
|
||||
Async methods are intended to be non-blocking operations. An `await` expression in an async method doesn't block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method.
|
||||
|
||||
The `async` and `await` keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. It's possible to use `Task.Run` to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available.
|
||||
|
||||
The async-based approach to asynchronous programming is preferable to existing approaches in almost every case. In particular, this approach is better than the `BackgroundWorker` class for I/O-bound operations because the code is simpler and there is no need to guard against race conditions.
|
||||
In combination with the `Task.Run` method, async programming is better than `BackgroundWorker` for CPU-bound operations because async programming separates the coordination details of running the code from the work that `Task.Run` transfers to the thread pool.
|
||||
|
||||
### Naming Convention
|
||||
|
||||
By convention, methods that return commonly awaitable types (for example, `Task`, `Task<T>`, `ValueTask`, `ValueTask<T>`) should have names that end with *Async*. Methods that start an asynchronous operation but do not return an awaitable type should not have names that end with *Async*, but may start with "Begin", "Start", or some other verb to suggest this method does not return or throw the result of the operation.
|
||||
|
||||
## [Async Return Types](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/async-return-types)
|
||||
|
||||
### `Task` return type
|
||||
|
||||
Async methods that don't contain a return statement or that contain a return statement that doesn't return an operand usually have a return type of `Task`. Such methods return `void` if they run synchronously.
|
||||
If a `Task` return type is used for an async method, a calling method can use an `await` operator to suspend the caller's completion until the called async method has finished.
|
||||
|
||||
### `Task<TResult>` return type
|
||||
|
||||
The `Task<TResult>` return type is used for an async method that contains a return statement in which the operand is `TResult`.
|
||||
|
||||
The `Task<TResult>.Result` property is a **blocking property**. If it's accessed it before its task is finished, the thread that's currently active is blocked until the task completes and the value is available.
|
||||
In most cases, access the value by using `await` instead of accessing the property directly.
|
||||
|
||||
### `void` return type
|
||||
|
||||
The `void` return type is used in asynchronous event handlers, which require a `void` return type. For methods other than event handlers that don't return a value, it's best to return a `Task` instead, because an async method that returns `void` can't be awaited.
|
||||
Any caller of such a method must continue to completion without waiting for the called async method to finish. The caller must be independent of any values or exceptions that the async method generates.
|
||||
|
||||
The caller of a void-returning async method *can't catch exceptions thrown from the method*, and such unhandled exceptions are likely to cause the application to fail.
|
||||
If a method that returns a `Task` or `Task<TResult>` throws an exception, the exception is stored in the returned task. The exception is re-thrown when the task is awaited.
|
||||
Therefore, make sure that any async method that can produce an exception has a return type of `Task` or `Task<TResult>` and that calls to the method are awaited.
|
||||
|
||||
### Generalized async return types and `ValueTask<TResult>`
|
||||
|
||||
Starting with C# 7.0, an async method can return any type that has an accessible `GetAwaiter` method.
|
||||
|
||||
Because `Task` and `Task<TResult>` are **reference types**, memory allocation in performance-critical paths, particularly when allocations occur in tight loops, can adversely affect performance. Support for generalized return types means that it's possible to return a lightweight **value type** instead of a reference type to avoid additional memory allocations.
|
||||
|
||||
.NET provides the `System.Threading.Tasks.ValueTask<TResult>` structure as a lightweight implementation of a generalized task-returning value. To use the `System.Threading.Tasks.ValueTask<TResult>` type, add the **System.Threading.Tasks.Extensions** NuGet package to the project.
|
||||
|
||||
### Async Composition
|
||||
|
||||
```cs
|
||||
public async Task DoOperationsConcurrentlyAsync()
|
||||
{
|
||||
Task[] tasks = new Task[3];
|
||||
tasks[0] = DoOperation0Async();
|
||||
tasks[1] = DoOperation1Async();
|
||||
tasks[2] = DoOperation2Async();
|
||||
|
||||
// At this point, all three tasks are running at the same time.
|
||||
|
||||
// Now, we await them all.
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
public async Task<int> GetFirstToRespondAsync()
|
||||
{
|
||||
// Call two web services; take the first response.
|
||||
Task<int>[] tasks = new[] { WebService1Async(), WebService2Async() };
|
||||
|
||||
// Await for the first one to respond.
|
||||
Task<int> firstTask = await Task.WhenAny(tasks);
|
||||
|
||||
// Return the result.
|
||||
return await firstTask;
|
||||
}
|
||||
```
|
||||
|
||||
### Execution & Synchronization Context
|
||||
|
||||
When the program’s execution reaches an `await` expression for an operation that doesn’t complete immediately, the code generated for that `await` will ensure that the
|
||||
current execution context has been captured.
|
||||
When the asynchronous operation completes, the remainder of the method will be executed through the execution context.
|
||||
The execution context handles certain contextual information that needs to flow when one method invokes another (even when it does so indirectly)
|
||||
|
||||
While all `await` expressions capture the *execution context*, the decision of whether to flow *synchronization context* as well is controlled by the type being awaited.
|
||||
|
||||
Sometimes, it's better to avoid getting the synchronization context involved.
|
||||
If work starting from a UI thread is performed, but there is no particular need to remain on that thread, scheduling every continuation through the synchronization context is unnecessary overhead.
|
||||
|
||||
If the asynchronous operation is a `Task`, `Task<T>`, `ValueTask` or `ValueTask<T>`, it's possible to discard the *synchronization context* by calling the `ConfigureAwait(false)`.
|
||||
This returns a different representation of the asynchronous operation, and if this iss awaited that instead of the original task, it will ignore the current `SynchronizationContext` if there is one.
|
||||
|
||||
```cs
|
||||
private async Task DownloadFileAsync(string fileName)
|
||||
{
|
||||
await OperationAsync(fileName).ConfigureAwait(false); // discarding original context
|
||||
}
|
||||
```
|
||||
|
||||
When writing libraries in most cases you it'ss best to call `ConfigureAwait(false)` anywhere `await` is used.
|
||||
This is because continuing via the synchronization context can be expensive, and in some cases it can introduce the possibility of deadlock occurring.
|
||||
|
||||
The only exceptions are when are doing something that positively requires the synchronization context to be preserved, or it's know for certain that the library will only ever be used in
|
||||
application frameworks that do not set up a synchronization context.
|
||||
(ASP.NET Core applications do not use synchronization contexts, so it generally doesn’t matter whether or not `ConfigureAwait(false)` is called in those)
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Argument Validation
|
||||
|
||||
Inside an `async` method, the compiler treats all exceptions in the same way: none are allowed to pass up the stack as in a normal method, and they will always be reported by faulting the returned task.
|
||||
This is true even of exceptions thrown before the first `await`.
|
||||
|
||||
If the calling method immediately calls `await` on the return task, this won’t matter much—it will see the exception in any case.
|
||||
But some code may choose not to wait immediately, in which case it won’t see the argument exception until later.
|
||||
|
||||
```cs
|
||||
async Task<string> MethodWithValidationAsync(string argument)
|
||||
{
|
||||
if(sting.IsNullOrEmpty(argument))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(argument)); // will be thrown on await of MethodWithValidationAsync
|
||||
}
|
||||
|
||||
// [...]
|
||||
|
||||
return await LongOperationAsync();
|
||||
}
|
||||
```
|
||||
|
||||
In cases where you want to throw this kind of exception straightaway, the usual technique is to write a normal method that validates the arguments before calling an async method that does the
|
||||
work, and to make that second method either private or local.
|
||||
|
||||
```cs
|
||||
// not marked with async, exception propagate directly to caller
|
||||
public static Task<string> MethodWithValidationAsync(string argument)
|
||||
{
|
||||
if(sting.IsNullOrEmpty(argument))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(argument)); // thrown immediately
|
||||
}
|
||||
|
||||
return ActualMethodAsync(argument); // pass up task of inner method
|
||||
}
|
||||
|
||||
private static async Task<string> ActualMethodAsync(string argument)
|
||||
{
|
||||
// [...]
|
||||
return await LongOperationAsync();
|
||||
}
|
||||
```
|
||||
|
||||
**NOTE**: `await` extracts only the first exception of an `AggregateException`, this can cause the loss of information if a task (or group of tasks) has more than one error.
|
296
dotnet/C#/collections.md
Normal file
296
dotnet/C#/collections.md
Normal file
|
@ -0,0 +1,296 @@
|
|||
# C# Collections
|
||||
|
||||
## Arrays
|
||||
|
||||
An array is an object that contains multiple elements of a particular type. The number of elements is fixed for the lifetime of the array, so it must be specified when the array is created.
|
||||
|
||||
An array type is always a reference type, regardless of the element type. Nonetheless, the choice between reference type and value type elements makes a significant difference in an array's behavior.
|
||||
|
||||
```cs
|
||||
type[] array = new type[dimension];
|
||||
type array[] = new type[dimension]; //invalid
|
||||
|
||||
type[] array = {value1, value2, ..., valueN}; // initializer
|
||||
var array = new type[] {value1, value2, ..., valueN}; // initializer (var type needs new operator)
|
||||
var array = new[] {value1, value2, ..., valueN}; // initializer w/ element type inference (var type needs new operator), can be used as method arg
|
||||
|
||||
array[index]; // value access
|
||||
array[index] = value; // value assignment
|
||||
array.Length; // dimension of the array
|
||||
|
||||
// from IEnumerable<T>
|
||||
array.OfType<Type>(); // filter array based on type, returns IEnumerable<Type>
|
||||
```
|
||||
|
||||
### [Array Methods](https://docs.microsoft.com/en-us/dotnet/api/system.array?view=netcore-3.1#methods)
|
||||
|
||||
```cs
|
||||
// overloaded search methods
|
||||
Array.IndexOf(array, item); // return index of searched item in passed array
|
||||
Array.LastIndexOf(array, item); // return index of searched item staring from the end of the array
|
||||
Array.FindIndex(array, Predicate<T>) // returns the index of the first item matching the predicate (can be lambda function)
|
||||
Array.FindLastIndex(array, Predicate<T>) // returns the index of the last item matching the predicate (can be lambda function)
|
||||
Array.Find(array, Predicate<T>) // returns the value of the first item matching the predicate (can be lambda function)
|
||||
Array.FindLast(array, Predicate<T>) // returns the value of the last item matching the predicate (can be lambda function)
|
||||
Array.FindAll(array, Predicate<T>) // returns array of all items matching the predicate (can be lambda function)
|
||||
Array.BinarySearch(array, value) // Searches a SORTED array for a value, using a binary search algorithm; returns the index of the found item
|
||||
|
||||
Array.Sort(array);
|
||||
Array.Reverse(array); // reverses the order of array elements
|
||||
Array.Clear(start_index, x); //removes reference to x elements starting at start index. Dimension of array unchanged (cleared elements value is set tu null)
|
||||
Array.Resize(ref array, target_dimension); //expands or shrinks the array dimension. Shrinking drops trailing values. Array passed by reference.
|
||||
|
||||
// Copies elements from an Array starting at the specified index and pastes them to another Array starting at the specified destination index.
|
||||
Array.Copy(sourceArray, sourceStartIndex, destinationArray, destinationStartIndex, numItemsToCopy);
|
||||
// Copies elements from an Array starting at the first element and pastes them into another Array starting at the first element.
|
||||
Array.Copy(sourceArray, destinationArray, numItemsToCopy);
|
||||
Array.Clone(); // returns a shallow copy of the array
|
||||
```
|
||||
|
||||
### Multidimensional Arrays
|
||||
|
||||
C# supports two multidimensional array forms: [jagged][jagg_arrays] arrays and [rectangular][rect_arrays] arrays (*matrices*).
|
||||
|
||||
[jagg_arrays]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/arrays/jagged-arrays
|
||||
[rect_arrays]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/arrays/multidimensional-arrays
|
||||
|
||||
```cs
|
||||
//specify first dimension
|
||||
type[][] jagged = new type[][]
|
||||
{
|
||||
new[] {item1, item2, item3},
|
||||
new[] {item1},
|
||||
new[] {item1, item2},
|
||||
...
|
||||
}
|
||||
|
||||
// shorthand
|
||||
type[][] jagged =
|
||||
{
|
||||
new[] {item1, item2, item3},
|
||||
new[] {item1},
|
||||
new[] {item1, item2},
|
||||
...
|
||||
}
|
||||
|
||||
// matrices
|
||||
type[,] matrix = new type[n, m]; // n * m matrix
|
||||
type[,] matrix = {{}, {}, {}, ...}; // {} for each row to initialize
|
||||
type[, ,] tensor = new type[n, m, o] // n * m * o tensor
|
||||
|
||||
matrix.Length; // total number of elements (n * m)
|
||||
matrix.GetLength(int dimension); // get the size of a particular direction
|
||||
// row = 0, column = 1, ...
|
||||
```
|
||||
|
||||
## [Lists](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1)
|
||||
|
||||
`List<T>` stores sequences of elements. It can grow or shrink, allowing to add or remove elements.
|
||||
|
||||
```cs
|
||||
using System.Collections.Generics;
|
||||
|
||||
List<T> list = new List<T>();
|
||||
List<T> list = new List<T> {item_1, ...}; // initialized usable since list implements IEnumerable<T> and has Add() method (even extension method)
|
||||
List<T> list = new List<T>(dimension); // set list starting dimension
|
||||
List<T> list = new List<T>(IEnumerable<T>); // create a list from an enumerable collection
|
||||
|
||||
|
||||
list.Add(item); //item insertion into the list
|
||||
list.AddRange(IEnumerable<T> collection); // insert multiple items
|
||||
list.Insert(index, item); // insert an item at the specified index
|
||||
list.InsertRange(index, item); // insert items at the specified index
|
||||
|
||||
list.IndexOf(item); // return index of searched item in passed list
|
||||
list.LastIndexOf(item); // return index of searched item staring from the end of the array
|
||||
list.FindIndex(Predicate<T>) // returns the index of the first item matching the predicate (can be lambda function)
|
||||
list.FindLastIndex(Predicate<T>) // returns the index of the last item matching the predicate (can be lambda function)
|
||||
list.Find(Predicate<T>) // returns the value of the first item matching the predicate (can be lambda function)
|
||||
list.FindLast(Predicate<T>) // returns the value of the last item matching the predicate (can be lambda function)
|
||||
list.FindAll(Predicate<T>) // returns list of all items matching the predicate (can be lambda function)
|
||||
list.BinarySearch(value) // Searches a SORTED list for a value, using a binary search algorithm; returns the index of the found item
|
||||
|
||||
list.Remove(item); // remove item from list
|
||||
list.RemoveAt(index); // remove item at specified position
|
||||
list.RemoveRange(index, quantity); // remove quantity items at specified position
|
||||
|
||||
list.Contains(item); // check if item is in the list
|
||||
list.TrueForAll(Predicate<T>); // Determines whether every element matches the conditions defined by the specified predicate
|
||||
|
||||
list[index]; // access to items by index
|
||||
list[index] = value; // modify to items by index
|
||||
list.Count; // number of items in the list
|
||||
|
||||
list.Sort(); // sorts item in crescent order
|
||||
list.Reverse(); // Reverses the order of the elements in the list
|
||||
|
||||
// from IEnumerable<T>
|
||||
list.OfType<Type>(); // filter list based on type, returns IEnumerable<Type>
|
||||
list.OfType<Type>().ToList(); // filter list based on type, returns List<Type>
|
||||
```
|
||||
|
||||
## [Iterators](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/iterators)
|
||||
|
||||
An iterator can be used to step through collections such as lists and arrays.
|
||||
|
||||
An iterator method or `get` accessor performs a custom iteration over a collection. An iterator method uses the `yield return` statement to return each element one at a time.
|
||||
When a `yield return` statement is reached, the current location in code is remembered. Execution is restarted from that location the next time the iterator function is called.
|
||||
|
||||
It's possible to use a `yield break` statement or exception to end the iteration.
|
||||
|
||||
**Note**: Since an iterator returns an `IEnumerable<T>` is can be used to implement a `GetEnumerator()`.
|
||||
|
||||
```cs
|
||||
// simple iterator
|
||||
public static System.Collections.IEnumerable<int> IterateRange(int start = 0, int end)
|
||||
{
|
||||
for(int i = start; i < end; i++){
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## List & Sequence Interfaces
|
||||
|
||||
### [`IEnumerable<T>`](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerable-1)
|
||||
|
||||
Exposes the enumerator, which supports a simple iteration over a collection of a specified type.
|
||||
|
||||
```cs
|
||||
public interface IEnumerable<out T> : IEnumerable
|
||||
{
|
||||
IEnumerator<T> GetEnumerator(); // return an enumerator
|
||||
}
|
||||
|
||||
// iterate through a collection
|
||||
public interface IEnumerator<T>
|
||||
{
|
||||
// properties
|
||||
object Current { get; } // Get the element in the collection at the current position of the enumerator.
|
||||
|
||||
// methods
|
||||
void IDisposable.Dispose(); // Perform application-defined tasks associated with freeing, releasing, or resetting unmanaged resources
|
||||
bool MoveNext(); // Advance the enumerator to the next element of the collection.
|
||||
void Reset(); // Set the enumerator to its initial position, which is before the first element in the collection.
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: must call `Dispose()` on enumerators once finished with them, because many of them rely on this. `Reset()` is legacy and can, in some situations, throw `NotSupportedException()`.
|
||||
|
||||
### [`ICollection<T>`](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.icollection-1)
|
||||
|
||||
```cs
|
||||
public interface ICollection<T> : IEnumerable<T>
|
||||
{
|
||||
// properties
|
||||
int Count { get; } // Get the number of elements contained in the ICollection<T>
|
||||
bool IsReadOnly { get; } // Get a value indicating whether the ICollection<T> is read-only
|
||||
|
||||
// methods
|
||||
void Add (T item); // Add an item to the ICollection<T>
|
||||
void Clear (); // Removes all items from the ICollection<T>
|
||||
bool Contains (T item); // Determines whether the ICollection<T> contains a specific value
|
||||
IEnumerator GetEnumerator (); // Returns an enumerator that iterates through a collection
|
||||
bool Remove (T item); // Removes the first occurrence of a specific object from the ICollection<T>
|
||||
}
|
||||
```
|
||||
|
||||
### [`IList<T>`](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.ilist-1)
|
||||
|
||||
```cs
|
||||
public interface IList<T> : ICollection<T>, IEnumerable<T>
|
||||
{
|
||||
// properties
|
||||
int Count { get; } // Get the number of elements contained in the ICollection<T>
|
||||
bool IsReadOnly { get; } // Get a value indicating whether the ICollection<T> is read-only
|
||||
T this[int index] { get; set; } // Get or set the element at the specified index
|
||||
|
||||
// methods
|
||||
void Add (T item); // Add an item to the ICollection<T>
|
||||
void Clear (); // Remove all items from the ICollection<T>
|
||||
bool Contains (T item); // Determine whether the ICollection<T> contains a specific value
|
||||
void CopyTo (T[] array, int arrayIndex); // Copy the elements of the ICollection<T> to an Array, starting at a particular Array index
|
||||
IEnumerator GetEnumerator (); // Return an enumerator that iterates through a collection
|
||||
int IndexOf (T item); // Determine the index of a specific item in the IList<T>
|
||||
void Insert (int index, T item); // Insert an item to the IList<T> at the specified index
|
||||
bool Remove (T item); // Remove the first occurrence of a specific object from the ICollection<T>
|
||||
oid RemoveAt (int index); // Remove the IList<T> item at the specified index
|
||||
}
|
||||
```
|
||||
|
||||
## [Dictionaries](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2)
|
||||
|
||||
[ValueCollection](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2.valuecollection)
|
||||
[KeyCollection](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2.keycollection)
|
||||
|
||||
**Notes**:
|
||||
|
||||
- Enumerating a dictionary will return `KeyValuePair<TKey, TValue>`.
|
||||
- The `Dictionary<TKey, TValue>` collection class relies on hashes to offer fast lookup (`TKey` should have a good `GetHashCode()`).
|
||||
|
||||
```cs
|
||||
Dictionary<TKey, TValue> dict = new Dictionary<TKey, TValue>(); // init empty dict
|
||||
Dictionary<TKey, TValue> dict = new Dictionary<TKey, TValue>(IEqualityComparer<TKey>); // specify key comparer (TKey must implement Equals() and GetHashCode())
|
||||
|
||||
// initializer (implicitly uses Add method)
|
||||
Dictionary<TKey, TValue> dict =
|
||||
{
|
||||
{ key, value }
|
||||
{ key, value },
|
||||
...
|
||||
}
|
||||
|
||||
// object initializer
|
||||
Dictionary<TKey, TValue> dict =
|
||||
{
|
||||
[key] = value,
|
||||
[key] = value,
|
||||
...
|
||||
}
|
||||
|
||||
// indexer access
|
||||
dict[key]; // read value associated with key (throws KeyNotFoundException if key does not exist)
|
||||
dict[key] = value; // modify value associated with key (throws KeyNotFoundException if key does not exist)
|
||||
|
||||
dict.Count; // number of key-value pair stored in the dict
|
||||
dict.Keys; // Dictionary<TKey,TValue>.KeyCollection containing the keys of the dict
|
||||
dict.Values; // Dictionary<TKey,TValue>.ValueCollection containing the values of the dict
|
||||
|
||||
dict.Add(key, value); // ArgumentException if the key already exists
|
||||
dict.Clear(); // empty the dictionary
|
||||
dict.ContainsKey(key); // check if a key is in the dictionary
|
||||
dict.ContainsValue(value); // check if a value is in the dictionary
|
||||
dict.Remove(key); // remove a key-value pair
|
||||
dict.Remove(key, out var); // remove key-value pair and copy TValue to var parameter
|
||||
dict.TryAdd(key, value); // adds a key-value pair; returns true if pair is added, false otherwise
|
||||
dict.TryGetValue(key, out var); // put the value associated with kay in the var parameter; true if the dict contains an element with the specified key, false otherwise.
|
||||
```
|
||||
|
||||
## [Sets](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.hashset-1)
|
||||
|
||||
Collection of non duplicate items.
|
||||
|
||||
```cs
|
||||
HashSet<T> set = new HashSet<T>();
|
||||
|
||||
set.Add(T); // adds an item to the set; true if the element is added, false if the element is already present.
|
||||
set.Clear(); //Remove all elements from a HashSet<T> object.
|
||||
set.Contains(T); // Determine whether a HashSet<T> object contains the specified element.
|
||||
set.CopyTo(T[]); // Coy the elements of a HashSet<T> object to an array.
|
||||
set.CopyTo(T[], arrayIndex); // Copy the elements of a HashSet<T> object to an array, starting at the specified array index.
|
||||
set.CopyTo(T[], arrayIndex, count); // Copies the specified number of elements of a HashSet<T> object to an array, starting at the specified array index.
|
||||
set.CreateSetComparer(); // Return an IEqualityComparer object that can be used for equality testing of a HashSet<T> object.
|
||||
set.ExceptWith(IEnumerable<T>); // Remove all elements in the specified collection from the current HashSet<T> object.
|
||||
set.IntersectWith(IEnumerable<T>); // Modify the current HashSet<T> object to contain only elements that are present in that object and in the specified collection.
|
||||
set.IsProperSubsetOf(IEnumerable<T>); // Determine whether a HashSet<T> object is a proper subset of the specified collection.
|
||||
set.IsProperSupersetOf(IEnumerable<T>); // Determine whether a HashSet<T> object is a proper superset of the specified collection.
|
||||
set.IsSubsetOf(IEnumerable<T>); // Determine whether a HashSet<T> object is a subset of the specified collection.
|
||||
set.IsSupersetOf(IEnumerable<T>); // Determine whether a HashSet<T> object is a superset of the specified collection.
|
||||
set.Overlaps(IEnumerable<T>); // Determine whether the current HashSet<T> object and a specified collection share common elements.
|
||||
set.Remove(T); // Remove the specified element from a HashSet<T> object.
|
||||
set.RemoveWhere(Predicate<T>); // Remove all elements that match the conditions defined by the specified predicate from a HashSet<T> collection.
|
||||
set.SetEquals(IEnumerable<T>); // Determine whether a HashSet<T> object and the specified collection contain the same elements.
|
||||
set.SymmetricExceptWith(IEnumerable<T>); // Modify the current HashSet<T> object to contain only elements that are present either in that object or in the specified collection, but not both.
|
||||
set.UnionWith(IEnumerable<T>); // Modify the current HashSet<T> object to contain all elements that are present in itself, the specified collection, or both.
|
||||
set.TryGetValue(T, out T); // Search the set for a given value and returns the equal value it finds, if any.
|
||||
```
|
84
dotnet/C#/linq.md
Normal file
84
dotnet/C#/linq.md
Normal file
|
@ -0,0 +1,84 @@
|
|||
# LINQ
|
||||
|
||||
## LINQ to Objects
|
||||
|
||||
<!-- Page: 423/761 of "Ian Griffiths - Programming C# 8.0 - Build Cloud, Web, and Desktop Applications.pdf" -->
|
||||
|
||||
The term **LINQ to Objects** refers to the use of LINQ queries with any `IEnumerable` or `IEnumerable<T>` collection directly, without the use of an intermediate LINQ provider or API such as [LINQ to SQL](https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/linq/) or [LINQ to XML](https://docs.microsoft.com/en-us/dotnet/standard/linq/linq-xml-overview).
|
||||
|
||||
LINQ to Objects will be used when any `IEnumerable<T>` is specified as the source, unless a more specialized provider is available.
|
||||
|
||||
### Query Expressions
|
||||
|
||||
All query expressions are required to begin with a `from` clause, which specifies the source of the query.
|
||||
The final part of the query is a `select` (or `group`) clause. This determines the final output of the query and its system type.
|
||||
|
||||
```cs
|
||||
// query expression
|
||||
var result = from item in enumerable select item;
|
||||
|
||||
// where clause
|
||||
var result = from item in enumerable where condition select item;
|
||||
|
||||
// ordering
|
||||
var result = from item in enumerable orderby item.property select item; // ordered IEnumerable
|
||||
|
||||
// let clause, assign expression to variable to avoid re-evaluation on each cycle
|
||||
var result = from item in enumerable let tmp = <sub-expr> ... // BEWARE: compiled code has a lot of overhead to satisfy let clause
|
||||
|
||||
// grouping (difficult to re-implement to obtain better performance)
|
||||
var result = from item in enumerable group item by item.property; // returns IEnumerable<IGrouping<TKey,TElement>>
|
||||
```
|
||||
|
||||
### How Query Expressions Expand
|
||||
|
||||
The compiler converts all query expressions into one or more method calls. Once it has done that, the LINQ provider is selected through exactly the same mechanisms that C# uses for any other method call.
|
||||
The compiler does not have any built-in concept of what constitutes a LINQ provider.
|
||||
|
||||
```cs
|
||||
// expanded query expression
|
||||
var result = Enumerable.Where(item => condition).Select(item => item);
|
||||
```
|
||||
|
||||
The `Where` and `Select` methods are examples of LINQ operators. A LINQ operator is nothing more than a method that conforms to one of the standard patterns.
|
||||
|
||||
### Methods on `Enumerable` or `IEnumerable<T>`
|
||||
|
||||
```cs
|
||||
Enumerable.Range(int start, int end); // IEnumerable<int> of values between start & end
|
||||
|
||||
IEnumerable<TSource>.Select(Func<TSource, TResult> selector); // map
|
||||
IEnumerable<TSource>.Where(Func<T, bool> predicate); // filter
|
||||
|
||||
IEnumerable<T>.FirstOrDefault(); // first element of IEnumerable or default(T) if empty
|
||||
IEnumerable<T>.FirstOrDefault(T default); // specify returned default
|
||||
IEnumerable<T>.FirstOrDefault(Func<T, bool> predicate); // first element to match predicate or default(T)
|
||||
// same for LastOrDefault & SingleOrDefault
|
||||
|
||||
IEnumerable<T>.Chunk(size); // chunk an enumerable into slices of a fixed size
|
||||
|
||||
// T must implement IComparable<T>
|
||||
IEnumerable<T>.Max();
|
||||
IEnumerable<T>.Min();
|
||||
|
||||
// allow finding maximal or minimal elements using a key selector
|
||||
IEnumerable<TSource>.MaxBy(Func<TSource, TResult> selector);
|
||||
IEnumerable<TSource>.MinBy(Func<TSource, TResult> selector);
|
||||
|
||||
IEnumerable<T>.All(Func<T, bool> predicate); // check if condition is true for all elements
|
||||
IEnumerable<T>.Any(Func<T, bool> predicate); // check if condition is true for at least one element
|
||||
|
||||
IEnumerable<T>.Concat(IEnumerable<T> enumerable);
|
||||
|
||||
// Applies a specified function to the corresponding elements of two sequences, producing a sequence of the results.
|
||||
IEnumerable<TFirst>.Zip(IEnumerable<TSecond> enumerable, Func<TFirst, TSecond, TResult> func);
|
||||
IEnumerable<TFirst>.Zip(IEnumerable<TSecond> enumerable); // Produces a sequence of tuples with elements from the two specified sequences.
|
||||
```
|
||||
|
||||
**NOTE**: `Enumerable` provides a set of `static` methods for querying objects that implement `IEnumerable<T>`. Most methods are extensions of `IEnumerable<T>`
|
||||
|
||||
```cs
|
||||
Enumerable.Method(IEnumerable<T> source, args);
|
||||
// if extension method same as
|
||||
IEnumerable<T>.Method(args);
|
||||
```
|
168
dotnet/C#/reactive-extensions.md
Normal file
168
dotnet/C#/reactive-extensions.md
Normal file
|
@ -0,0 +1,168 @@
|
|||
# Reactive Extensions (Rx)
|
||||
|
||||
[ReactiveX](https://reactivex.io "ReactiveX website")
|
||||
|
||||
The **Reactive Extensions** for .NET, or **Rx**, are designed for working with asynchronous and event-based sources of information.
|
||||
Rx provides services that help orchestrate and synchronize the way code reacts to data from these kinds of sources.
|
||||
|
||||
Rx's fundamental abstraction, `IObservable<T>`, represents a sequence of items, and its operators are defined as extension methods for this interface.
|
||||
|
||||
This might sound a lot like LINQ to Objects, and there are similarities, not only does `IObservable<T>` have a lot in common with `IEnumerable<T>`, but Rx also supports almost all of the standard LINQ operators.
|
||||
|
||||
The difference is that in Rx, sequences are less passive. Unlike `IEnumerable<T>`, Rx sources do not wait to be asked for their items, nor can the consumer
|
||||
of an Rx source demand to be given the next item. Instead, Rx uses a *push* model in which *the source notifies* its recipients when items are available.
|
||||
|
||||
Because Rx implements standard LINQ operators, it's possible to write queries against a live source. Rx goes beyond standard LINQ, adding its own operators that take into account the temporal nature of a live event source.
|
||||
|
||||
## Fundamental Interfaces
|
||||
|
||||
The two most important types in Rx are the `IObservable<T>` and `IObserver<T>` interfaces.
|
||||
They are important enough to be in the System namespace. The other parts of Rx are in the `System.Reactive` NuGet package.
|
||||
|
||||
```cs
|
||||
public interface IObservable<out T>
|
||||
{
|
||||
IDisposable Subscribe(IObserver<T> observer);
|
||||
}
|
||||
|
||||
public interface IObserver<in T>
|
||||
{
|
||||
void OnCompleted();
|
||||
void OnError(Exception error);
|
||||
void OnNext(T value);
|
||||
}
|
||||
```
|
||||
|
||||
The fundamental abstraction in Rx, `IObservable<T>`, is implemented by *event sources*. Instead of using the `event` keyword, it models events as a *sequence of items*.
|
||||
An `IObservable<T>` provides items to subscribers as and when it’s ready to do so.
|
||||
|
||||
It's possible to subscribe to a source by passing an implementation of `IObserver<T>` to the `Subscribe` method.
|
||||
The source will invoke `OnNext` when it wants to report events, and it can call `OnCompleted` to indicate that there will be no further activity.
|
||||
If the source wants to report an error, it can call `OnError`.
|
||||
Both `OnCompleted` and `OnError` indicate the end of the stream, an observable should not call any further methods on the observer after that.
|
||||
|
||||
## Operators
|
||||
|
||||
### Chaining Operators
|
||||
|
||||
Most operators operate on an Observable and return an Observable. This allows to apply these operators one after the other, in a chain.
|
||||
Each operator in the chain modifies the Observable that results from the operation of the previous operator.
|
||||
A chain of Observable operators do not operate independently on the original Observable that originates the chain,
|
||||
but they operate *in turn*, each one operating on the Observable generated by the operator immediately previous in the chain.
|
||||
|
||||
### Creating Observables
|
||||
|
||||
Operators that originate new Observables.
|
||||
|
||||
- `Create`: create an Observable from scratch by calling observer methods programmatically
|
||||
- `Defer`: do not create the Observable until the observer subscribes, and create a fresh Observable for each observer
|
||||
- `Empty/Never/Throw`: create Observables that have very precise and limited behavior
|
||||
- `From*`: convert some other object or data structure into an Observable
|
||||
- `Interval`: create an Observable that emits a sequence of integers spaced by a particular time interval
|
||||
- `Return (aka Just)`: convert an object or a set of objects into an Observable that emits that or those objects
|
||||
- `Range`: create an Observable that emits a range of sequential integers
|
||||
- `Repeat`: create an Observable that emits a particular item or sequence of items repeatedly
|
||||
- `Start`: create an Observable that emits the return value of a function
|
||||
- `Timer`: create an Observable that emits a single item after a given delay
|
||||
|
||||
### Transforming Observables
|
||||
|
||||
Operators that transform items that are emitted by an Observable.
|
||||
|
||||
- `Buffer`: periodically gather items from an Observable into bundles and emit these bundles rather than emitting the items one at a time
|
||||
- `SelectMany (aka FlatMap)`: transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable
|
||||
- `GroupBy`: divide an Observable into a set of Observables that each emit a different group of items from the original Observable, organized by key
|
||||
- `Select (aka Map)`: transform the items emitted by an Observable by applying a function to each item
|
||||
- `Scan`: apply a function to each item emitted by an Observable, sequentially, and emit each successive value
|
||||
- `Window`: periodically subdivide items from an Observable into Observable windows and emit these windows rather than emitting the items one at a time
|
||||
|
||||
### Filtering Observables
|
||||
|
||||
Operators that selectively emit items from a source Observable.
|
||||
|
||||
- `Throttle (aka Debounce)`: only emit an item from an Observable if a particular timespan has passed without it emitting another item
|
||||
- `Distinct`: suppress duplicate items emitted by an Observable
|
||||
- `ElementAt`: emit only item n emitted by an Observable
|
||||
- `Where (aka Filter)`: emit only those items from an Observable that pass a predicate test
|
||||
- `First`: emit only the first item, or the first item that meets a condition, from an Observable
|
||||
- `IgnoreElements`: do not emit any items from an Observable but mirror its termination notification
|
||||
- `Last`: emit only the last item emitted by an Observable
|
||||
- `Sample`: emit the most recent item emitted by an Observable within periodic time intervals
|
||||
- `Skip`: suppress the first n items emitted by an Observable
|
||||
- `SkipLast`: suppress the last n items emitted by an Observable
|
||||
- `Take`: emit only the first n items emitted by an Observable
|
||||
- `TakeLast`: emit only the last n items emitted by an Observable
|
||||
|
||||
### Combining Observables
|
||||
|
||||
Operators that work with multiple source Observables to create a single Observable
|
||||
|
||||
- `And/Then/When`: combine sets of items emitted by two or more Observables by means of Pattern and Plan intermediaries
|
||||
- `CombineLatest`: when an item is emitted by either of two Observables, combine the latest item emitted by each Observable via a specified function and emit items based on the results of this function
|
||||
- `Join`: combine items emitted by two Observables whenever an item from one Observable is emitted during a time window defined according to an item emitted by the other Observable
|
||||
- `Merge`: combine multiple Observables into one by merging their emissions
|
||||
- `StartWith`: emit a specified sequence of items before beginning to emit the items from the source Observable
|
||||
- `Switch`: convert an Observable that emits Observables into a single Observable that emits the items emitted by the most-recently-emitted of those Observables
|
||||
- `Zip`: combine the emissions of multiple Observables together via a specified function and emit single items for each combination based on the results of this function
|
||||
|
||||
### Error Handling Operators
|
||||
|
||||
Operators that help to recover from error notifications from an Observable
|
||||
|
||||
- `Catch`: recover from an onError notification by continuing the sequence without error
|
||||
- `Retry`: if a source Observable sends an onError notification, resubscribe to it in the hopes that it will complete without error
|
||||
|
||||
### Observable Utility Operators
|
||||
|
||||
A toolbox of useful Operators for working with Observables
|
||||
|
||||
- `Delay`: shift the emissions from an Observable forward in time by a particular amount
|
||||
- `Do`: register an action to take upon a variety of Observable lifecycle events
|
||||
- `Materialize/Dematerialize`: represent both the items emitted and the notifications sent as emitted items, or reverse this process
|
||||
- `ObserveOn`: specify the scheduler on which an observer will observe this Observable
|
||||
- `Synchronize (aka Serialize)`: force an Observable to make serialized calls and to be well-behaved
|
||||
- `Subscribe`: operate upon the emissions and notifications from an Observable
|
||||
- `SubscribeOn`: specify the scheduler an Observable should use when it is subscribed to
|
||||
- `TimeInterval`: convert an Observable that emits items into one that emits indications of the amount of time elapsed between those emissions
|
||||
- `Timeout`: mirror the source Observable, but issue an error notification if a particular period of time elapses without any emitted items
|
||||
- `Timestamp`: attach a timestamp to each item emitted by an Observable
|
||||
- `Using`: create a disposable resource that has the same lifespan as the Observable
|
||||
|
||||
### Conditional and Boolean Operators
|
||||
|
||||
Operators that evaluate one or more Observables or items emitted by Observables
|
||||
|
||||
- `All`: determine whether all items emitted by an Observable meet some criteria
|
||||
- `Amb`: given two or more source Observables, emit all of the items from only the first of these Observables to emit an item
|
||||
- `Contains`: determine whether an Observable emits a particular item or not
|
||||
- `DefaultIfEmpty`: emit items from the source Observable, or a default item if the source Observable emits nothing
|
||||
- `SequenceEqual`: determine whether two Observables emit the same sequence of items
|
||||
- `SkipUntil`: discard items emitted by an Observable until a second Observable emits an item
|
||||
- `SkipWhile`: discard items emitted by an Observable until a specified condition becomes false
|
||||
- `TakeUntil`: discard items emitted by an Observable after a second Observable emits an item or terminates
|
||||
- `TakeWhile`: discard items emitted by an Observable after a specified condition becomes false
|
||||
|
||||
### Mathematical and Aggregate Operators
|
||||
|
||||
Operators that operate on the entire sequence of items emitted by an Observable
|
||||
|
||||
- `Average`: calculates the average of numbers emitted by an Observable and emits this average
|
||||
- `Concat`: emit the emissions from two or more Observables without interleaving them
|
||||
- `Count`: count the number of items emitted by the source Observable and emit only this value
|
||||
- `Max`: determine, and emit, the maximum-valued item emitted by an Observable
|
||||
- `Min`: determine, and emit, the minimum-valued item emitted by an Observable
|
||||
- `Aggregate (aka Reduce)`: apply a function to each item emitted by an Observable, sequentially, and emit the final value
|
||||
- `Sum`: calculate the sum of numbers emitted by an Observable and emit this sum
|
||||
|
||||
### Connectable Observable Operators
|
||||
|
||||
Specialty Observables that have more precisely-controlled subscription dynamics
|
||||
|
||||
- `Connect`: instruct a connectable Observable to begin emitting items to its subscribers
|
||||
- `Publish`: convert an ordinary Observable into a connectable Observable
|
||||
- `RefCount`: make a Connectable Observable behave like an ordinary Observable
|
||||
- `Replay`: ensure that all observers see the same sequence of emitted items, even if they subscribe after the Observable has begun emitting items
|
||||
|
||||
### Operators to Convert Observables
|
||||
|
||||
- `To*`: convert an Observable into another object or data structure
|
51
dotnet/C#/unit-tests.md
Normal file
51
dotnet/C#/unit-tests.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
# Unit Testing
|
||||
|
||||
[UnitTest Overloaded Methods](https://stackoverflow.com/a/5666591/8319610)
|
||||
[Naming standards for unit tests](https://osherove.com/blog/2005/4/3/naming-standards-for-unit-tests.html)
|
||||
|
||||
## xUnit
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Project.Tests
|
||||
{
|
||||
public class ClassTest
|
||||
{
|
||||
[Fact]
|
||||
public void TestMethod()
|
||||
{
|
||||
Assert.Equal(expected, actual); // works on collections
|
||||
Assert.True(bool);
|
||||
Assert.False(bool);
|
||||
Assert.NotNull(nullable);
|
||||
|
||||
// Verifies that all items in the collection pass when executed against action
|
||||
Assert.All<T>(IEnumerable<T> collection, Action<T> action);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test Setup & Teardown
|
||||
|
||||
xUnit.net creates a new instance of the test class for every test that is run, so any code which is placed into the constructor of the test class will be run for every single test.
|
||||
This makes the constructor a convenient place to put reusable context setup code.
|
||||
|
||||
For context cleanup, add the `IDisposable` interface to the test class, and put the cleanup code in the `Dispose()` method.
|
||||
|
||||
## Mocking with Moq
|
||||
|
||||
```cs
|
||||
var mockObj = new Mock<MockedType>();
|
||||
|
||||
mockObj.Setup(m => m.Method(It.IsAny<InputType>())).Returns(value);
|
||||
mockObj.Object; // get mock
|
||||
|
||||
// check that the invocation is forwarded to the mock, n times
|
||||
mockObj.Verify(m => m.Method(It.IsAny<InputType>()), Times.Once());
|
||||
|
||||
// check that the invocation is forwarded to the mock with a specific input
|
||||
mockObj.Verify(m => m.Method(input), Times.Once());
|
||||
```
|
164
dotnet/asp.net/Razor Syntax.md
Normal file
164
dotnet/asp.net/Razor Syntax.md
Normal file
|
@ -0,0 +1,164 @@
|
|||
# [Razor Syntax](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor)
|
||||
|
||||
## Markup
|
||||
|
||||
```cs
|
||||
@page // set this as razor page
|
||||
|
||||
@model <App>.Models.Entity // if MVC set type of elements passed to the view
|
||||
@model <Page>Model // if Razor page set underlying class
|
||||
|
||||
@* razor comment *@
|
||||
|
||||
// substitute @variable with it's value
|
||||
<tag>@variable</tag>
|
||||
|
||||
@{
|
||||
// razor code block
|
||||
// can contain C# or HTML
|
||||
|
||||
Model // access to passed @model (MVC)
|
||||
}
|
||||
|
||||
@if (condition) { }
|
||||
|
||||
@for (init, condition, iteration) { }
|
||||
|
||||
@Model.Property // display Property value (MVC)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tag Helpers (ASP.NET Core)
|
||||
|
||||
**Tag helpers** are reusable components for automating the generation of HTML in Razor Pages. Tag helpers target specific HTML tags.
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
<!-- tag helpers for a lin in ASP.NET MVC -->
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
|
||||
```
|
||||
|
||||
### Managing Tag Helpers
|
||||
|
||||
The `@addTagHelper` directive makes Tag Helpers available to the view. Generally, the view file is `Pages/_ViewImports.cshtml`, which by default is inherited by all files in the `Pages` folder and subfolders, making Tag Helpers available.
|
||||
|
||||
```cs
|
||||
@using <App>
|
||||
@namespace <App>.Pages // or <Project>.Models
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
```
|
||||
|
||||
The first parameter after `@addTagHelper` specifies the Tag Helpers to load (`*` for all Tag Helpers), and the second parameter (e.g. `Microsoft.AspNetCore.Mvc.TagHelpers`) specifies the assembly containing the Tag Helpers.
|
||||
`Microsoft.AspNetCore.Mvc.TagHelpers` is the assembly for the built-in ASP.NET Core Tag Helpers.
|
||||
|
||||
#### Opting out of individual elements
|
||||
|
||||
It's possible to disable a Tag Helper at the element level with the Tag Helper opt-out character (`!`)
|
||||
|
||||
```cshtml
|
||||
<!-- disable email validation -->
|
||||
<!span asp-validation-for="Email" ></!span>
|
||||
```
|
||||
|
||||
### Explicit Tag Helpers
|
||||
|
||||
The `@tagHelperPrefix` directive allows to specify a tag prefix string to enable Tag Helper support and to make Tag Helper usage explicit.
|
||||
|
||||
```cshtml
|
||||
@tagHelpersPrefix th:
|
||||
```
|
||||
|
||||
### Important Tag Helpers (`asp-`) & HTML Helpers (`@Html`)
|
||||
|
||||
[Understanding Html Helpers](https://stephenwalther.com/archive/2009/03/03/chapter-6-understanding-html-helpers)
|
||||
|
||||
```cs
|
||||
@model <App>.Models.Entity
|
||||
|
||||
// Display the name of the property
|
||||
@Html.DisplayNameFor(model => model.EntityProp)
|
||||
@nameof(Model.EntityProp)
|
||||
|
||||
// Display the value of the property
|
||||
@Html.DisplayFor(model => model.EntityProp)
|
||||
@Model.EntityProp
|
||||
|
||||
<from>
|
||||
// use the property as the label, eventually w/ [DisplayName("...")]
|
||||
<label asp-for="EntityProp"></label>
|
||||
@Html.LabelFor()
|
||||
|
||||
// automatically set the value at form compilation and submission
|
||||
<input asp-for="EntityProp"/>
|
||||
@Html.EditorFor()
|
||||
</from>
|
||||
|
||||
// route config is {Controller}/{Action}/{Id?}
|
||||
<a asp-controller="<Controller>" asp-action="<Action>">Link</a> // link to /Controller/Action
|
||||
<a asp-controller="<Controller>" asp-action="<Action>" asp-route-Id="@model.Id">Link</a> // link to /Controller/Action/Id
|
||||
@Html.ActionLink("<Link Text>", "<Action>", "<Controller>", new { @HTmlAttribute = value, Id = value }) // link to /Controller/Action/Id
|
||||
|
||||
// link to /Controller/Action?queryParameter=value
|
||||
@Html.ActionLink("<Link Text>", "<Action>", "<Controller>", new { @HTmlAttribute = value, queryParameter = value })
|
||||
<a asp-controller="<Controller>" asp-action="<Action>" asp-route-queryParameter="value">Link</a> // asp-route-* for query strings
|
||||
```
|
||||
|
||||
### [Select Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms)
|
||||
|
||||
[StackOverflow](https://stackoverflow.com/a/34624217)
|
||||
[SelectList Docs](https://docs.microsoft.com/en-us/dotnet/api/system.web.mvc.selectlist)
|
||||
|
||||
In `ViewModel.cs`:
|
||||
|
||||
```cs
|
||||
class ViewModel
|
||||
{
|
||||
public int EntityId { get; set; } // value selected in form ends up here
|
||||
|
||||
// object has numeric id and other props
|
||||
public SelectList Entities = new()
|
||||
|
||||
public ViewModel(){ } // parameterless constructor (NEEDED)
|
||||
}
|
||||
```
|
||||
|
||||
In `View.cs`
|
||||
|
||||
```cs
|
||||
@model ViewModel
|
||||
|
||||
<form asp-controller="Controller" asp-action="PostAction">
|
||||
|
||||
<select asp-for"EntityId" asp-items="Model.Entities">
|
||||
</select>
|
||||
|
||||
<button type="submit">Send<button>
|
||||
</form>
|
||||
```
|
||||
|
||||
In `Controller.cs`:
|
||||
|
||||
```cs
|
||||
public IActionResult GetAction()
|
||||
{
|
||||
var vm = new ViewModel();
|
||||
|
||||
vm.Entities = new SelectList(_context.Entities, "Id", "Text"); // fill SelectList
|
||||
vm.EntityId = value; // set selected option (OPTIONAL)
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult PostAction(ViewModel)
|
||||
{
|
||||
if(ModelState.IsValid)
|
||||
{
|
||||
// extract info from view model
|
||||
// save to db
|
||||
}
|
||||
}
|
||||
```
|
244
dotnet/asp.net/app-configuration.md
Normal file
244
dotnet/asp.net/app-configuration.md
Normal file
|
@ -0,0 +1,244 @@
|
|||
# ASP.NET Configuration
|
||||
|
||||
## `.csproj`
|
||||
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<!-- enable documentation comments (can be used for swagger) -->
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
||||
<!-- do not warn public classes w/o documentation comments -->
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
## `Program.cs`
|
||||
|
||||
```cs
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace App
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run(); // start and config ASP.NET Core App
|
||||
|
||||
// or start Blazor WASM Single Page App
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
|
||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
}
|
||||
|
||||
// for MVC, Razor Pages and Blazor Server
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>(); // config handled in Startup.cs
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `Startup.cs`
|
||||
|
||||
```cs
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.HttpsPolicy;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace App
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the DI container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// set db context for the app using the connection string
|
||||
services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
|
||||
|
||||
// Captures synchronous and asynchronous Exception instances from the pipeline and generates HTML error responses.
|
||||
services.AddDatabaseDeveloperPageExceptionFilter();
|
||||
|
||||
// use Razor Pages, runtime compilation needs Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation pkg
|
||||
services.AddRazorPages().AddRazorRuntimeCompilation();
|
||||
// or
|
||||
services.AddControllers(); // controllers w/o views
|
||||
//or
|
||||
services.AddControllersWithViews(); // MVC Controllers
|
||||
//or
|
||||
services.AddServerSideBlazor(); // needs Razor Pages
|
||||
|
||||
services.AddSignalR();
|
||||
|
||||
// set dependency injection lifetimes
|
||||
services.AddSingleton<ITransientService, ServiceImplementation>();
|
||||
services.AddScoped<ITransientService, ServiceImplementation>();
|
||||
services.AddTransient<ITransientService, ServiceImplementation>();
|
||||
|
||||
// add swagger
|
||||
services.AddSwaggerGen(options => {
|
||||
|
||||
// OPTIONAL: use xml comments for swagger documentation
|
||||
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/Home/Error");
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
// MVC routing
|
||||
endpoints.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}"
|
||||
);
|
||||
// or
|
||||
endpoints.MapControllers(); // map controllers w/o views
|
||||
// or
|
||||
endpoints.MapRazorPages();
|
||||
// or
|
||||
endpoints.MapBlazorHub(); // SignalR Hub for Blazor Server
|
||||
|
||||
endpoints.MapHub("/hub/endpoint"); // SignalR Hub
|
||||
endpoints.MapFallbackToPage("/_Host"); // fallback for razor server
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Application Settings
|
||||
|
||||
App settings are loaded (in order) from:
|
||||
|
||||
1. `appsettings.json`
|
||||
2. `appsettings.<Environment>.json`
|
||||
3. User Secrets
|
||||
|
||||
The environment is controlled by the env var `ASPNETCORE_ENVIRONMENT`. If a setting is present in multiple locations, the last one is used and overrides the previous ones.
|
||||
|
||||
### User Secrets
|
||||
|
||||
User secrets are specific to each machine and can be initialized with `dotnet user-secrets init`. Each application is linked with it's settings by a guid.
|
||||
|
||||
The settings are stored in:
|
||||
|
||||
- `%APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json` (Windows)
|
||||
- `~/.microsoft/usersecrets/<user_secrets_id>/secrets.json` (Linux/macOS)
|
||||
|
||||
Setting a value is done with `dotnet user-secrets set <key> <value>`, keys can be nested by separating each level with `:` or `__`.
|
||||
|
||||
## Options Pattern
|
||||
|
||||
The *options pattern* uses classes to provide strongly-typed access to groups of related settings.
|
||||
|
||||
```json
|
||||
{
|
||||
"SecretKey": "Secret key value",
|
||||
"TransientFaultHandlingOptions": {
|
||||
"Enabled": true,
|
||||
"AutoRetryDelay": "00:00:07"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```cs
|
||||
// options model for binding
|
||||
public class TransientFaultHandlingOptions
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public TimeSpan AutoRetryDelay { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
```cs
|
||||
// setup the options
|
||||
builder.Services.Configure<TransientFaultHandlingOptions>(builder.Configuration.GetSection<TransientFaultHandlingOptions>(nameof(Options)));
|
||||
builder.Services.Configure<TransientFaultHandlingOptions>(builder.Configuration.GetSection<TransientFaultHandlingOptions>(key));
|
||||
```
|
||||
|
||||
```cs
|
||||
class DependsOnOptions
|
||||
{
|
||||
private readonly IOptions<TransientFaultHandlingOptions> _options;
|
||||
|
||||
public DependsOnOptions(IOptions<TransientFaultHandlingOptions> options) => _options = options;
|
||||
}
|
||||
```
|
||||
|
||||
### [Options interfaces](https://docs.microsoft.com/en-us/dotnet/core/extensions/options#options-interfaces)
|
||||
|
||||
`IOptions<TOptions>`:
|
||||
|
||||
- Does not support:
|
||||
- Reading of configuration data after the app has started.
|
||||
- Named options
|
||||
- Is registered as a Singleton and can be injected into any service lifetime.
|
||||
|
||||
`IOptionsSnapshot<TOptions>`:
|
||||
|
||||
- Is useful in scenarios where options should be recomputed on every injection resolution, in scoped or transient lifetimes.
|
||||
- Is registered as Scoped and therefore cannot be injected into a Singleton service.
|
||||
- Supports named options
|
||||
|
||||
`IOptionsMonitor<TOptions>`:
|
||||
|
||||
- Is used to retrieve options and manage options notifications for `TOptions` instances.
|
||||
- Is registered as a Singleton and can be injected into any service lifetime.
|
||||
- Supports:
|
||||
- Change notifications
|
||||
- Named options
|
||||
- Reloadable configuration
|
||||
- Selective options invalidation (`IOptionsMonitorCache<TOptions>`)
|
416
dotnet/asp.net/blazor.md
Normal file
416
dotnet/asp.net/blazor.md
Normal file
|
@ -0,0 +1,416 @@
|
|||
# Blazor
|
||||
|
||||
Blazor apps are based on *components*. A **component** in Blazor is an element of UI, such as a page, dialog, or data entry form.
|
||||
|
||||
Components are .NET C# classes built into .NET assemblies that:
|
||||
|
||||
- Define flexible UI rendering logic.
|
||||
- Handle user events.
|
||||
- Can be nested and reused.
|
||||
- Can be shared and distributed as Razor class libraries or NuGet packages.
|
||||
|
||||

|
||||

|
||||
|
||||
The component class is usually written in the form of a Razor markup page with a `.razor` file extension. Components in Blazor are formally referred to as *Razor components*.
|
||||
|
||||
## Project Structure & Important Files
|
||||
|
||||
### Blazor Server Project Structure
|
||||
|
||||
```txt
|
||||
Project
|
||||
|-Properties
|
||||
| |- launchSettings.json
|
||||
|
|
||||
|-wwwroot --> static files
|
||||
| |-css
|
||||
| | |- site.css
|
||||
| | |- bootstrap
|
||||
| |
|
||||
| |- favicon.ico
|
||||
|
|
||||
|-Pages
|
||||
| |- _Host.cshtml --> fallback page
|
||||
| |- Component.razor
|
||||
| |- Index.razor
|
||||
| |- ...
|
||||
|
|
||||
|-Shared
|
||||
| |- MainLayout.razor
|
||||
| |- MainLayout.razor.css
|
||||
| |- ...
|
||||
|
|
||||
|- _Imports.razor --> @using imports
|
||||
|- App.razor --> component root of the app
|
||||
|
|
||||
|- appsettings.json --> application settings
|
||||
|- Program.cs --> App entry-point
|
||||
|- Startup.cs --> services and middleware configs
|
||||
```
|
||||
|
||||
### Blazor WASM Project Structure
|
||||
|
||||
```txt
|
||||
Project
|
||||
|-Properties
|
||||
| |- launchSettings.json
|
||||
|
|
||||
|-wwwroot --> static files
|
||||
| |-css
|
||||
| | |- site.css
|
||||
| | |- bootstrap
|
||||
| |
|
||||
| |- index.html
|
||||
| |- favicon.ico
|
||||
|
|
||||
|-Pages
|
||||
| |- Component.razor
|
||||
| |- Index.razor
|
||||
| |- ...
|
||||
|
|
||||
|-Shared
|
||||
| |- MainLayout.razor
|
||||
| |- MainLayout.razor.css
|
||||
| |- ...
|
||||
|
|
||||
|- _Imports.razor --> @using imports
|
||||
|- App.razor --> component root of the app
|
||||
|
|
||||
|- appsettings.json --> application settings
|
||||
|- Program.cs --> App entry-point
|
||||
```
|
||||
|
||||
### Blazor PWA Project Structure
|
||||
|
||||
```txt
|
||||
Project
|
||||
|-Properties
|
||||
| |- launchSettings.json
|
||||
|
|
||||
|-wwwroot --> static files
|
||||
| |-css
|
||||
| | |- site.css
|
||||
| | |- bootstrap
|
||||
| |
|
||||
| |- index.html
|
||||
| |- favicon.ico
|
||||
| |- manifest.json
|
||||
| |- service-worker.js
|
||||
| |- icon-512.png
|
||||
|
|
||||
|-Pages
|
||||
| |- Component.razor
|
||||
| |- Index.razor
|
||||
| |- ...
|
||||
|
|
||||
|-Shared
|
||||
| |- MainLayout.razor
|
||||
| |- MainLayout.razor.css
|
||||
| |- ...
|
||||
|
|
||||
|- _Imports.razor --> @using imports
|
||||
|- App.razor --> component root of the app
|
||||
|
|
||||
|- appsettings.json --> application settings
|
||||
|- Program.cs --> App entrypoint
|
||||
```
|
||||
|
||||
### `manifest.json`, `service-worker.js` (Blazor PWA)
|
||||
|
||||
[PWA](https://web.dev/progressive-web-apps/)
|
||||
[PWA MDN Docs](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps)
|
||||
[PWA Manifest](https://developer.mozilla.org/en-US/docs/Web/Manifest)
|
||||
[Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)
|
||||
|
||||
```json
|
||||
// manifest.json
|
||||
{
|
||||
"name": "<App Name>",
|
||||
"short_name": "<Short App Name>",
|
||||
"start_url": "./",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#03173d",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon-512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Common Blazor Files
|
||||
|
||||
### `App.razor`
|
||||
|
||||
```cs
|
||||
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p>Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
```
|
||||
|
||||
### `MainLayout.razor` (Blazor Server/WASM)
|
||||
|
||||
```cs
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
<NavMenu /> // NavMenu Component
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="top-row px-4">
|
||||
</div>
|
||||
|
||||
<div class="content px-4">
|
||||
@Body
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### `_Host.cshtml` (Blazor Server)
|
||||
|
||||
```html
|
||||
@page "/"
|
||||
@namespace BlazorServerDemo.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BlazorServerDemo</title>
|
||||
<base href="~/" />
|
||||
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
|
||||
<link href="css/site.css" rel="stylesheet" />
|
||||
<link href="BlazorServerDemo.styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<component type="typeof(App)" render-mode="ServerPrerendered" />
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
<environment include="Staging,Production">
|
||||
An error has occurred. This application may no longer respond until reloaded.
|
||||
</environment>
|
||||
<environment include="Development">
|
||||
An unhandled exception has occurred. See browser dev tools for details.
|
||||
</environment>
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
|
||||
<script src="_framework/blazor.server.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Components (`.razor`)
|
||||
|
||||
[Blazor Components](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/)
|
||||
|
||||
```cs
|
||||
@page "/route/{RouteParameter}" // make component accessible from a URL
|
||||
@page "/route/{RouteParameter?}" // specify route parameter as optional
|
||||
@page "/route/{RouteParameter:<type>}" // specify route parameter type
|
||||
|
||||
@namespace <Namespace> // set the component namespace
|
||||
@using <Namespace> // using statement
|
||||
@inherits BaseType // inheritance
|
||||
@attribute [Attribute] // apply an attribute
|
||||
@inject Type objectName // dependency injection
|
||||
|
||||
// html of the page here
|
||||
|
||||
<Namespace.ComponentFolder.Component /> // access component w/o @using
|
||||
<Component Property="value"/> // insert component into page, passing attributes
|
||||
<Component @onclick="@CallbackMethod">
|
||||
@ChildContent // segment of UI content
|
||||
</Component>
|
||||
|
||||
@code {
|
||||
// component model (Properties, Methods, ...)
|
||||
|
||||
[Parameter] // capture attribute
|
||||
public Type Property { get; set; } = defaultValue;
|
||||
|
||||
[Parameter] // capture route parameters
|
||||
public type RouteParameter { get; set;}
|
||||
|
||||
[Parameter] // segment of UI content
|
||||
public RenderFragment ChildContent { get; set;}
|
||||
|
||||
private void CallbackMethod() { }
|
||||
}
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
### Blazor WASM
|
||||
|
||||
```cs
|
||||
// setup state singleton
|
||||
builder.Services.AddSingleton<StateContainer>();
|
||||
```
|
||||
|
||||
```cs
|
||||
// StateContainer singleton
|
||||
using System;
|
||||
|
||||
public class StateContainer
|
||||
{
|
||||
private int _counter;
|
||||
|
||||
public string Property
|
||||
{
|
||||
get => _counter;
|
||||
set
|
||||
{
|
||||
_counter = value;
|
||||
NotifyStateChanged(); // will trigger StateHasChanged(), causing a render
|
||||
}
|
||||
}
|
||||
|
||||
public event Action OnChange;
|
||||
|
||||
private void NotifyStateChanged() => OnChange?.Invoke();
|
||||
}
|
||||
```
|
||||
|
||||
```cs
|
||||
// component that changes the state
|
||||
@inject StateContainer State
|
||||
|
||||
// Delegate event handlers automatically trigger a UI render
|
||||
<button @onClick="@HandleClick">
|
||||
Change State
|
||||
</button>
|
||||
|
||||
@code {
|
||||
private void HandleClick()
|
||||
{
|
||||
State.Property += 1; // update state
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```cs
|
||||
// component that should be update on state change
|
||||
@implements IDisposable
|
||||
@inject StateContainer State
|
||||
|
||||
<p>Property: <b>@State.Property</b></p>
|
||||
|
||||
@code {
|
||||
|
||||
// StateHasChanged notifies the component that its state has changed.
|
||||
// When applicable, calling StateHasChanged causes the component to be rerendered.
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
State.OnChange += StateHasChanged;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
State.OnChange -= StateHasChanged;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Data Binding & Events
|
||||
|
||||
```cs
|
||||
<p>
|
||||
<button @on{DOM EVENT}="{DELEGATE}" />
|
||||
<button @on{DOM EVENT}="{DELEGATE}" @on{DOM EVENT}:preventDefault /> // prevent default action
|
||||
<button @on{DOM EVENT}="{DELEGATE}" @on{DOM EVENT}:preventDefault="{CONDITION}" /> // prevent default action if CONDITION is true
|
||||
|
||||
<button @on{DOM EVENT}="{DELEGATE}" @on{DOM EVENT}:stopPropagation />
|
||||
<button @on{DOM EVENT}="{DELEGATE}" @on{DOM EVENT}:stopPropagation="{CONDITION}" /> // stop event propagation if CONDITION is true
|
||||
|
||||
<button @on{DOM EVENT}="@(e => Property = value)" /> // change internal state w/ lambda
|
||||
<button @on{DOM EVENT}="@(e => DelegateAsync(e, value))" /> // invoke delegate w/ lambda
|
||||
|
||||
<input @ref="elementReference" />
|
||||
|
||||
<input @bind="{PROPERTY}" /> // updates variable on ONCHANGE event (focus loss)
|
||||
<input @bind="{PROPERTY}" @bind:event="{DOM EVENT}" /> // updates value on DOM EVENT
|
||||
<input @bind="{PROPERTY}" @bind:format="{FORMAT STRING}" /> // use FORMAT STRING to display value
|
||||
|
||||
<ChildComponent @bind-{PROPERTY}="{PROPERTY}" @bind-{PROPERTY}:event="{EVENT}" /> // bind to child component {PROPERTY}
|
||||
<ChildComponent @bind-{PROPERTY}="{PROPERTY}" @bind-{PROPERTY}:event="{PROPERTY}Changed" /> // bind to child component {PROPERTY}, listen for custom event
|
||||
</p>
|
||||
|
||||
@code {
|
||||
private ElementReference elementReference;
|
||||
|
||||
public string Property { get; set; }
|
||||
|
||||
public EventCallback<Type> PropertyChanged { get; set; } // custom event {PROPERTY}Changed
|
||||
|
||||
// invoke custom event
|
||||
public async Task DelegateAsync(EventArgs e, Type argument)
|
||||
{
|
||||
/* ... */
|
||||
|
||||
await PropertyChanged.InvokeAsync(e, argument); // notify parent bound prop has changed
|
||||
await elementReference.FocusAsync(); // focus an element in code
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**NOTE**: When a user provides an unparsable value to a data-bound element, the unparsable value is automatically reverted to its previous value when the bind event is triggered.
|
||||
|
||||
## Javascript/.NET Interop
|
||||
|
||||
[Call Javascript from .NET](https://docs.microsoft.com/en-us/aspnet/core/blazor/call-javascript-from-dotnet)
|
||||
[Call .NET from Javascript](https://docs.microsoft.com/en-us/aspnet/core/blazor/call-dotnet-from-javascript)
|
||||
|
||||
### Render Blazor components from JavaScript [C# 10]
|
||||
|
||||
To render a Blazor component from JavaScript, first register it as a root component for JavaScript rendering and assign it an identifier:
|
||||
|
||||
```cs
|
||||
// Blazor Server
|
||||
builder.Services.AddServerSideBlazor(options =>
|
||||
{
|
||||
options.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter");
|
||||
});
|
||||
|
||||
// Blazor WebAssembly
|
||||
builder.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter");
|
||||
```
|
||||
|
||||
Load Blazor into the JavaScript app (`blazor.server.js` or `blazor.webassembly.js`) and then render the component from JavaScript into a container element using the registered identifier, passing component parameters as needed:
|
||||
|
||||
```js
|
||||
let containerElement = document.getElementById('my-counter');
|
||||
await Blazor.rootComponents.add(containerElement, 'counter', { incrementAmount: 10 });
|
||||
```
|
||||
|
||||
### Blazor custom elements [C# 10]
|
||||
|
||||
Experimental support is also now available for building custom elements with Blazor using the Microsoft.AspNetCore.Components.CustomElements NuGet package.
|
||||
Custom elements use standard HTML interfaces to implement custom HTML elements.
|
||||
|
||||
To create a custom element using Blazor, register a Blazor root component as custom elements like this:
|
||||
|
||||
```cs
|
||||
options.RootComponents.RegisterAsCustomElement<Counter>("my-counter");
|
||||
```
|
132
dotnet/asp.net/filters.md
Normal file
132
dotnet/asp.net/filters.md
Normal file
|
@ -0,0 +1,132 @@
|
|||
# [Filters](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters)
|
||||
|
||||
**Filters** in ASP.NET Core allow code to be run _before_ or _after_ specific stages in the request processing pipeline.
|
||||
|
||||
Built-in filters handle tasks such as:
|
||||
|
||||
- Authorization (preventing access to resources a user isn't authorized for).
|
||||
- Response caching (short-circuiting the request pipeline to return a cached response).
|
||||
|
||||
Custom filters can be created to handle cross-cutting concerns. Examples of cross-cutting concerns include error handling, caching, configuration, authorization, and logging. Filters avoid duplicating code.
|
||||
|
||||
## **How filters work**
|
||||
|
||||
Filters run within the _ASP.NET Core action invocation pipeline_, sometimes referred to as the _filter pipeline_. The filter pipeline runs after ASP.NET Core selects the action to execute.
|
||||
|
||||

|
||||

|
||||
|
||||
## **Filter types**
|
||||
|
||||
Each filter type is executed at a different stage in the filter pipeline:
|
||||
|
||||
- **Authorization filters** run first and are used to determine whether the user is authorized for the request. Authorization filters short-circuit the pipeline if the request is not authorized.
|
||||
- **Resource filters**:
|
||||
- Run after authorization.
|
||||
- `OnResourceExecuting` runs code before the rest of the filter pipeline. For example, `OnResourceExecuting` runs code before model binding.
|
||||
- `OnResourceExecuted` runs code after the rest of the pipeline has completed.
|
||||
- **Action filters**:
|
||||
- Run code immediately before and after an action method is called.
|
||||
- Can change the arguments passed into an action.
|
||||
- Can change the result returned from the action.
|
||||
- Are **not** supported in Razor Pages.
|
||||
- **Exception filters** apply global policies to unhandled exceptions that occur before the response body has been written to.
|
||||
- **Result filters** run code immediately before and after the execution of action results. They run only when the action method has executed successfully. They are useful for logic that must surround view or formatter execution.
|
||||
|
||||
## **Implementation**
|
||||
|
||||
Filters support both synchronous and asynchronous implementations through different interface definitions.
|
||||
|
||||
For example, `OnActionExecuting` is called before the action method is called. `OnActionExecuted` is called after the action method returns.
|
||||
Asynchronous filters define an `On-Stage-ExecutionAsync` method, for example `OnActionExecutionAsync`.
|
||||
|
||||
Interfaces for multiple filter stages can be implemented in a single class.
|
||||
|
||||
## **Built-in filter attributes**
|
||||
|
||||
ASP.NET Core includes built-in _attribute-based_ filters that can be subclassed and customized.
|
||||
Several of the filter interfaces have corresponding attributes that can be used as base classes for custom implementations.
|
||||
|
||||
Filter attributes:
|
||||
|
||||
- [ActionFilterAttribute](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filters.actionfilterattribute)
|
||||
- [ExceptionFilterAttribute](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filters.exceptionfilterattribute)
|
||||
- [ResultFilterAttribute](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filters.resultfilterattribute)
|
||||
- [FormatFilterAttribute](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.formatfilterattribute)
|
||||
- [ServiceFilterAttribute](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.servicefilterattribute)
|
||||
- [TypeFilterAttribute](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.typefilterattribute)
|
||||
|
||||
## **Filter scopes**
|
||||
|
||||
A filter can be added to the pipeline at one of three *scopes*:
|
||||
|
||||
- Using an attribute on a controller action. Filter attributes cannot be applied to Razor Pages handler methods.
|
||||
|
||||
```cs
|
||||
// services.AddScoped<CustomActionFilterAttribute>();
|
||||
[ServiceFilter(typeof(CustomActionFilterAttribute))]
|
||||
public IActionResult Index()
|
||||
{
|
||||
return Content("Header values by configuration.");
|
||||
}
|
||||
```
|
||||
|
||||
- Using an attribute on a controller or Razor Page.
|
||||
|
||||
```cs
|
||||
// services.AddControllersWithViews(options => { options.Filters.Add(new CustomResponseFilterAttribute(args)); });
|
||||
[CustomResponseFilterAttribute(args)]
|
||||
public class SampleController : Controller
|
||||
|
||||
// or
|
||||
|
||||
[CustomResponseFilterAttribute(args)]
|
||||
[ServiceFilter(typeof(CustomActionFilterAttribute))]
|
||||
public class IndexModel : PageModel
|
||||
```
|
||||
|
||||
- Globally for all controllers, actions, and Razor Pages.
|
||||
|
||||
```cs
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllersWithViews(options =>
|
||||
{
|
||||
options.Filters.Add(typeof(CustomActionFilter));
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Filter Order of Execution
|
||||
|
||||
When there are multiple filters for a particular stage of the pipeline, scope determines the default order of filter execution. Global filters surround class filters, which in turn surround method filters.
|
||||
|
||||
As a result of filter nesting, the *after* code of filters runs in the reverse order of the *before* code. The filter sequence:
|
||||
|
||||
- The *before* code of global filters.
|
||||
- The *before* code of controller and Razor Page filters.
|
||||
- The *before* code of action method filters.
|
||||
- The *after* code of action method filters.
|
||||
- The *after* code of controller and Razor Page filters.
|
||||
- The *after* code of global filters.
|
||||
|
||||
### Cancellation and Short-Circuiting
|
||||
|
||||
The filter pipeline can be short-circuited by setting the `Result` property on the `ResourceExecutingContext` parameter provided to the filter method.
|
||||
|
||||
```cs
|
||||
public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
|
||||
{
|
||||
public void OnResourceExecuting(ResourceExecutingContext context)
|
||||
{
|
||||
context.Result = new ContentResult()
|
||||
{
|
||||
Content = "Resource unavailable - header not set."
|
||||
};
|
||||
}
|
||||
|
||||
public void OnResourceExecuted(ResourceExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
207
dotnet/asp.net/middleware.md
Normal file
207
dotnet/asp.net/middleware.md
Normal file
|
@ -0,0 +1,207 @@
|
|||
# [Middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware)
|
||||
|
||||
Middleware is software that's assembled into an app pipeline to handle requests and responses. Each component:
|
||||
|
||||
- Chooses whether to pass the request to the next component in the pipeline.
|
||||
- Can perform work before and after the next component in the pipeline.
|
||||
|
||||
Request delegates are used to build the request pipeline. The request delegates handle each HTTP request.
|
||||
|
||||
Request delegates are configured using [Run][Run_docs], [Map][Map_docs], and [Use][Use_docs] extension methods.
|
||||
|
||||
An individual request delegate can be specified in-line as an anonymous method (called in-line middleware), or it can be defined in a reusable class.
|
||||
These reusable classes and in-line anonymous methods are *middleware*, also called *middleware components*.
|
||||
|
||||
Each middleware component in the request pipeline is responsible for invoking the next component in the pipeline or short-circuiting the pipeline.
|
||||
When a middleware short-circuits, it's called a *terminal middleware* because it prevents further middleware from processing the request.
|
||||
|
||||
[Use_docs]: https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.useextensions.use
|
||||
[Run_docs]: https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.runextensions.run
|
||||
[Map_docs]: https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.mapextensions.map
|
||||
|
||||
## Middleware Pipeline
|
||||
|
||||
The ASP.NET Core request pipeline consists of a sequence of request delegates, called one after the other.
|
||||
|
||||

|
||||
|
||||
Each delegate can perform operations before and after the next delegate. Exception-handling delegates should be called early in the pipeline, so they can catch exceptions that occur in later stages of the pipeline. It's possible to chain multiple request delegates together with `Use`.
|
||||
|
||||
The *next* parameter represents the next delegate in the pipeline. It's possible to short-circuit the pipeline by *not calling* the next parameter.
|
||||
When a delegate doesn't pass a request to the next delegate, it's called *short-circuiting the request pipeline*.
|
||||
Short-circuiting is often desirable because it avoids unnecessary work.
|
||||
|
||||
It's possible to perform actions both *before* and *after* the next delegate:
|
||||
|
||||
```cs
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
// "inline" middleware, best if in own class
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
// Do work that doesn't write to the Response.
|
||||
await next.Invoke();
|
||||
// Do logging or other work that doesn't write to the Response.
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Run` delegates don't receive a next parameter. The first `Run` delegate is always terminal and terminates the pipeline.
|
||||
|
||||
```cs
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
// "inline" middleware, best if in own class
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
// Do work that doesn't write to the Response.
|
||||
await next.Invoke();
|
||||
// Do logging or other work that doesn't write to the Response.
|
||||
});
|
||||
|
||||
app.Run(async context =>
|
||||
{
|
||||
// no invocation of next
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Middleware Order
|
||||
|
||||

|
||||

|
||||
|
||||
The Endpoint middleware executes the filter pipeline for the corresponding app type.
|
||||
|
||||
The order that middleware components are added in the `Startup.Configure` method defines the order in which the middleware components are invoked on requests and the reverse order for the response. The order is **critical** for security, performance, and functionality.
|
||||
|
||||
```cs
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseDatabaseErrorPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
// app.UseCookiePolicy();
|
||||
|
||||
app.UseRouting();
|
||||
// app.UseRequestLocalization();
|
||||
// app.UseCors();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
// app.UseSession();
|
||||
// app.UseResponseCompression();
|
||||
// app.UseResponseCaching();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapRazorPages();
|
||||
endpoints.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
[Built-in Middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/#built-in-middleware)
|
||||
|
||||
## Branching the Middleware Pipeline
|
||||
|
||||
`Map` extensions are used as a convention for branching the pipeline. `Map` branches the request pipeline based on matches of the given request path.
|
||||
If the request path starts with the given path, the branch is executed.
|
||||
|
||||
When `Map` is used, the matched path segments are removed from `HttpRequest.Path` and appended to `HttpRequest.PathBase` for each request.
|
||||
|
||||
`MapWhen` branches the request pipeline based on the result of the given predicate.
|
||||
Any *predicate* of type `Func<HttpContext, bool>` can be used to map requests to a new branch of the pipeline.
|
||||
|
||||
`UseWhen` also branches the request pipeline based on the result of the given predicate.
|
||||
Unlike with `MapWhen`, this branch is rejoined to the main pipeline if it doesn't short-circuit or contain a terminal middleware.
|
||||
|
||||
## Custom Middleware Classes
|
||||
|
||||
Middleware is generally encapsulated in a class and exposed with an extension method.
|
||||
|
||||
```cs
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace <App>
|
||||
{
|
||||
public class CustomMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public CustomMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
// Do work that doesn't write to the Response.
|
||||
await _next(context); // Call the next delegate/middleware in the pipeline
|
||||
// Do logging or other work that doesn't write to the Response.
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The middleware class **must** include:
|
||||
|
||||
- A public constructor with a parameter of type [RequestDelegate][RequestDelegate_docs].
|
||||
- A public method named `Invoke` or `InvokeAsync`. This method must:
|
||||
- Return a `Task`.
|
||||
- Accept a first parameter of type [HttpContext][HttpConrext_Docs].
|
||||
|
||||
[RequestDelegate_docs]: https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.requestdelegate
|
||||
[HttpConrext_Docs]: https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httpcontext
|
||||
|
||||
## Middleware Extension Methods
|
||||
|
||||
```cs
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace <App>
|
||||
{
|
||||
public static class MiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseCustom(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<CustomMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```cs
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
// other middlewares
|
||||
|
||||
app.UseCustom(); // add custom middleware in the pipeline
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
}
|
||||
```
|
233
dotnet/asp.net/minimal-api.md
Normal file
233
dotnet/asp.net/minimal-api.md
Normal file
|
@ -0,0 +1,233 @@
|
|||
# Minimal API
|
||||
|
||||
**NOTE**: Requires .NET 6+
|
||||
|
||||
```cs
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddSingleton<IService, Service>();
|
||||
builder.Services.AddScoped<IService, Service>();
|
||||
builder.Services.AddTransient<IService, Service>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// [...]
|
||||
|
||||
app.Run();
|
||||
//or
|
||||
app.RunAsync();
|
||||
```
|
||||
|
||||
## Swagger
|
||||
|
||||
```cs
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
// [...]
|
||||
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
|
||||
// add returned content metadata to Swagger
|
||||
app.MapGet("/route", Handler).Produces<Type>(statusCode);
|
||||
|
||||
// add request body contents metadata to Swagger
|
||||
app.MapPost("/route", Handler).Accepts<Type>(contentType);
|
||||
```
|
||||
|
||||
## MVC
|
||||
|
||||
```cs
|
||||
builder.Services.AddControllersWithViews();
|
||||
//or
|
||||
builder.Services.AddControllers();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/Home/Error");
|
||||
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||
```
|
||||
|
||||
## Routing, Handlers & Results
|
||||
|
||||
To define routes and handlers using Minimal APIs, use the `Map(Get|Post|Put|Delete)` methods.
|
||||
|
||||
```cs
|
||||
// the dependencies are passed as parameters in the handler delegate
|
||||
app.MapGet("/route/{id}", (IService service, int id) => {
|
||||
|
||||
return entity is not null ? Results.Ok(entity) : Results.NotFound();
|
||||
});
|
||||
|
||||
// pass delegate to use default values
|
||||
app.MapGet("/search/{id}", Search);
|
||||
IResult Search(int id, int? page = 1, int? pageSize = 10) { /* ... */ }
|
||||
```
|
||||
|
||||
### Route Groups
|
||||
|
||||
The `MapGroup()` extension method, which helps organize groups of endpoints with a common prefix.
|
||||
It allows for customizing entire groups of endpoints with a singe call to methods like `RequireAuthorization()` and `WithMetadata()`.
|
||||
|
||||
```cs
|
||||
var group = app.MapGroup("<route-prefix>");
|
||||
|
||||
group.MapGet("/", GetAllTodos); // route: /<route-prefix>
|
||||
group.MapGet("/{id}", GetTodo); // route: /<route-prefix>/{id}
|
||||
|
||||
// [...]
|
||||
```
|
||||
|
||||
### `TypedResults`
|
||||
|
||||
The `Microsoft.AspNetCore.Http.TypedResults` static class is the “typed” equivalent of the existing `Microsoft.AspNetCore.Http.Results` class.
|
||||
It's possible to use `TypedResults` in minimal APIs to create instances of the in-framework `IResult`-implementing types and preserve the concrete type information.
|
||||
|
||||
```cs
|
||||
public static async Task<IResult> GetAllTodos(TodoDb db)
|
||||
{
|
||||
return TypedResults.Ok(await db.Todos.ToArrayAsync());
|
||||
}
|
||||
```
|
||||
|
||||
```cs
|
||||
[Fact]
|
||||
public async Task GetAllTodos_ReturnsOkOfObjectResult()
|
||||
{
|
||||
// Arrange
|
||||
var db = CreateDbContext();
|
||||
|
||||
// Act
|
||||
var result = await TodosApi.GetAllTodos(db);
|
||||
|
||||
// Assert: Check the returned result type is correct
|
||||
Assert.IsType<Ok<Todo[]>>(result);
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple Result Types
|
||||
|
||||
The `Results<TResult1, TResult2, TResultN>` generic union types, along with the `TypesResults` class, can be used to declare that a route handler returns multiple `IResult`-implementing concrete types.
|
||||
|
||||
```cs
|
||||
// Declare that the lambda returns multiple IResult types
|
||||
app.MapGet("/todos/{id}", async Results<Ok<Todo>, NotFound> (int id, TodoDb db)
|
||||
{
|
||||
return await db.Todos.FindAsync(id) is Todo todo
|
||||
? TypedResults.Ok(todo)
|
||||
: TypedResults.NotFound();
|
||||
});
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
With Minimal APIs it's possible to access the contextual information by passing one of the following types as a parameter to your handler delegate:
|
||||
|
||||
- `HttpContext`
|
||||
- `HttpRequest`
|
||||
- `HttpResponse`
|
||||
- `ClaimsPrincipal`
|
||||
- `CancellationToken` (RequestAborted)
|
||||
|
||||
```cs
|
||||
app.MapGet("/hello", (ClaimsPrincipal user) => {
|
||||
return "Hello " + user.FindFirstValue("sub");
|
||||
});
|
||||
```
|
||||
|
||||
## OpenAPI
|
||||
|
||||
The `Microsoft.AspNetCore.OpenApi` package exposes a `WithOpenApi` extension method that generates an `OpenApiOperation` derived from a given endpoint’s route handler and metadata.
|
||||
|
||||
```cs
|
||||
app.MapGet("/todos/{id}", (int id) => ...)
|
||||
.WithOpenApi();
|
||||
|
||||
app.MapGet("/todos/{id}", (int id) => ...)
|
||||
.WithOpenApi(operation => {
|
||||
operation.Summary = "Retrieve a Todo given its ID";
|
||||
operation.Parameters[0].AllowEmptyValue = false;
|
||||
});
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
Using [Minimal Validation](https://github.com/DamianEdwards/MinimalValidation) by Damian Edwards.
|
||||
Alternatively it's possible to use [Fluent Validation](https://fluentvalidation.net/).
|
||||
|
||||
```cs
|
||||
app.MapPost("/widgets", (Widget widget) => {
|
||||
var isValid = MinimalValidation.TryValidate(widget, out var errors);
|
||||
|
||||
if(isValid)
|
||||
{
|
||||
return Results.Created($"/widgets/{widget.Name}", widget);
|
||||
}
|
||||
|
||||
return Results.BadRequest(errors);
|
||||
});
|
||||
|
||||
class Widget
|
||||
{
|
||||
[Required, MinLength(3)]
|
||||
public string? Name { get; set; }
|
||||
|
||||
public override string? ToString() => Name;
|
||||
}
|
||||
```
|
||||
|
||||
## JSON Serialization
|
||||
|
||||
```cs
|
||||
// Microsoft.AspNetCore.Http.Json.JsonOptions
|
||||
builder.Services.Configure<JsonOptions>(opt =>
|
||||
{
|
||||
opt.SerializerOptions.PropertyNamingPolicy = new SnakeCaseNamingPolicy();
|
||||
});
|
||||
```
|
||||
|
||||
## Authorization
|
||||
|
||||
```cs
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
// or
|
||||
builder.Services.AddAuthorization(options =>
|
||||
{
|
||||
// for all endpoints
|
||||
options.FallbackPolicy = new AuthorizationPolicyBuilder()
|
||||
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
|
||||
.RequireAuthenticatedUser();
|
||||
})
|
||||
|
||||
// [...]
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization(); // must come before routes
|
||||
|
||||
// [...]
|
||||
|
||||
app.MapGet("/alcohol", () => Results.Ok()).RequireAuthorization("<policy>"); // on specific endpoints
|
||||
app.MapGet("/free-for-all", () => Results.Ok()).AllowAnonymous();
|
||||
```
|
259
dotnet/asp.net/mvc.md
Normal file
259
dotnet/asp.net/mvc.md
Normal file
|
@ -0,0 +1,259 @@
|
|||
# ASP.NET (Core) MVC Web App
|
||||
|
||||
## Project Structure
|
||||
|
||||
```txt
|
||||
Project
|
||||
|-Properties
|
||||
| |- launchSettings.json
|
||||
|
|
||||
|-wwwroot --> location of static files
|
||||
| |-css
|
||||
| | |- site.css
|
||||
| |
|
||||
| |-js
|
||||
| | |- site.js
|
||||
| |
|
||||
| |-lib
|
||||
| | |- bootstrap
|
||||
| | |- jquery
|
||||
| | |- ...
|
||||
| |
|
||||
| |- favicon.ico
|
||||
|
|
||||
|-Model
|
||||
| |-ErrorViewModel.cs
|
||||
| |- Index.cs
|
||||
| |-...
|
||||
|
|
||||
|-Views
|
||||
| |-Home
|
||||
| | |- Index.cshtml
|
||||
| |
|
||||
| |-Shared
|
||||
| | |- _Layout.cshtml --> reusable default page layout
|
||||
| | |- _ValidationScriptsPartial --> jquery validation script imports
|
||||
| |
|
||||
| |- _ViewImports.cshtml --> shared imports and tag helpers for all views
|
||||
| |- _ViewStart.cshtml --> shared values for all views
|
||||
| |- ...
|
||||
|
|
||||
|-Controllers
|
||||
| |-HomeController.cs
|
||||
|
|
||||
|- appsettings.json
|
||||
|- Program.cs --> App entry-point
|
||||
|- Startup.cs --> App config
|
||||
```
|
||||
|
||||
**Note**: `_` prefix indicates page to be imported.
|
||||
|
||||
## Controllers
|
||||
|
||||
```cs
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using App.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace App.Controllers
|
||||
{
|
||||
public class CategoryController : Controller
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
// get db context through dependency injection
|
||||
public CategoryController(AppDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
// GET /Controller/Index
|
||||
public IActionResult Index()
|
||||
{
|
||||
IEnumerable<Entity> entities = _db.Entities;
|
||||
return View(Entities); // pass data to the @model
|
||||
}
|
||||
|
||||
// GET /Controller/Create
|
||||
public IActionResult Create()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
// POST /Controller/Create
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult Create(Entity entity) // receive data from the @model
|
||||
{
|
||||
_db.Entities.Add(entity);
|
||||
_db.SaveChanges();
|
||||
return RedirectToAction("Index"); // redirection
|
||||
}
|
||||
|
||||
// GET - /Controller/Edit
|
||||
public IActionResult Edit(int? id)
|
||||
{
|
||||
if(id == null || id == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
Entity entity = _db.Entities.Find(id);
|
||||
if (entity == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return View(entity); // return populated form for updating
|
||||
}
|
||||
|
||||
// POST /Controller/Edit
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult Edit(Entity entity)
|
||||
{
|
||||
if (ModelState.IsValid) // all rules in model have been met
|
||||
{
|
||||
_db.Entities.Update(entity);
|
||||
_db.SaveChanges();
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
return View(entity);
|
||||
}
|
||||
|
||||
// GET /controller/Delete
|
||||
public IActionResult Delete(int? id)
|
||||
{
|
||||
if (id == null || id == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
Entity entity = _db.Entities.Find(id);
|
||||
if (entity == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return View(entity); // return populated form for confirmation
|
||||
}
|
||||
|
||||
// POST /Controller/Delete
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult Delete(Entity entity)
|
||||
{
|
||||
if (ModelState.IsValid) // all rules in model have been met
|
||||
{
|
||||
_db.Entities.Remove(entity);
|
||||
_db.SaveChanges();
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
return View(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Data Validation
|
||||
|
||||
### Model Annotations
|
||||
|
||||
In `Entity.cs`:
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
|
||||
namespace App.Models
|
||||
{
|
||||
public class Entity
|
||||
{
|
||||
[DisplayName("Integer Number")]
|
||||
[Required]
|
||||
[Range(1, int.MaxValue, ErrorMessage = "Error Message")]
|
||||
public int IntProp { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Tag Helpers & Client Side Validation
|
||||
|
||||
In `View.cshtml`;
|
||||
|
||||
```cs
|
||||
<form method="post" asp-action="Create">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-4">
|
||||
<label asp-for="IntProp"></label>
|
||||
</div>
|
||||
|
||||
<div class="col-8">
|
||||
<input asp-for="IntProp" class="form-control"/>
|
||||
<span asp-validation-for="IntProp" class="text-danger"></span> // error message displayed here
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
// client side validation
|
||||
@section Scripts{
|
||||
@{ <partial name="_ValidationScriptsPartial" /> }
|
||||
}
|
||||
```
|
||||
|
||||
### Server Side Validation
|
||||
|
||||
```cs
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using App.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace App.Controllers
|
||||
{
|
||||
public class CategoryController : Controller
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
// get db context through dependency injection
|
||||
public CategoryController(AppDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
// GET /Controller/Index
|
||||
public IActionResult Index()
|
||||
{
|
||||
IEnumerable<Entity> entities = _db.Entities;
|
||||
return View(Entities); // pass data to the @model
|
||||
}
|
||||
|
||||
// GET /Controller/Create
|
||||
public IActionResult Create()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
// POST /Controller/Create
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult Create(Entity entity) // receive data from the @model
|
||||
{
|
||||
if (ModelState.IsValid) // all rules in model have been met
|
||||
{
|
||||
_db.Entities.Add(entity);
|
||||
_db.SaveChanges();
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
return View(entity); // return model and display error messages
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
236
dotnet/asp.net/razor-pages.md
Normal file
236
dotnet/asp.net/razor-pages.md
Normal file
|
@ -0,0 +1,236 @@
|
|||
# Razor Pages
|
||||
|
||||
## Project Structure
|
||||
|
||||
```txt
|
||||
Project
|
||||
|-Properties
|
||||
| |- launchSettings.json
|
||||
|
|
||||
|-wwwroot --> static files
|
||||
| |-css
|
||||
| | |- site.css
|
||||
| |
|
||||
| |-js
|
||||
| | |- site.js
|
||||
| |
|
||||
| |-lib
|
||||
| | |- jquery
|
||||
| | |- bootstrap
|
||||
| | |- ...
|
||||
| |
|
||||
| |- favicon.ico
|
||||
|
|
||||
|-Pages
|
||||
| |-Shared
|
||||
| | |- _Layout.cshtml --> reusable default page layout
|
||||
| | |- _ValidationScriptsPartial --> jquery validation script imports
|
||||
| |
|
||||
| |- _ViewImports.cshtml --> shared imports and tag helpers for all views
|
||||
| |- _ViewStart.cshtml --> shared values for all views
|
||||
| |- Index.cshtml
|
||||
| |- Index.cshtml.cs
|
||||
| |- ...
|
||||
|
|
||||
|- appsettings.json --> application settings
|
||||
|- Program.cs --> App entry-point
|
||||
|- Startup.cs
|
||||
```
|
||||
|
||||
**Note**: `_` prefix indicates page to be imported
|
||||
|
||||
Razor Pages components:
|
||||
|
||||
- Razor Page (UI/View - `.cshtml`)
|
||||
- Page Model (Handlers - `.cshtml.cs`)
|
||||
|
||||
in `Index.cshtml`:
|
||||
|
||||
```cs
|
||||
@page // mark as Razor Page
|
||||
@model IndexModel // Link Page Model
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Page Title" // same as <title>Page Title</title>
|
||||
}
|
||||
|
||||
// body contents
|
||||
```
|
||||
|
||||
in `Page.cshtml.cs`:
|
||||
|
||||
```cs
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace App.Pages
|
||||
{
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
// HTTP Method
|
||||
public void OnGet() { }
|
||||
|
||||
// HTTP Method
|
||||
public void OnPost() { }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Razor Page
|
||||
|
||||
```cs
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace App.Pages
|
||||
{
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
private readonly ApplicationDbContext _db; // EF DB Context
|
||||
|
||||
// Get DBContext through DI
|
||||
public IndexModel(ApplicationDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[BindProperty] // assumed to be received on POST
|
||||
public IEnumerable<Entity> Entities { get; set; }
|
||||
|
||||
// HTTP Method Handler
|
||||
public async Task OnGet()
|
||||
{
|
||||
// get data from DB (example operation)
|
||||
Entities = await _db.Entities.ToListAsync();
|
||||
}
|
||||
|
||||
// HTTP Method Handler
|
||||
public async Task<IActionResult> OnPost()
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
// save to DB (example operation)
|
||||
await _db.Entities.AddAsync(Entity);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return RedirectToPage("Index");
|
||||
}
|
||||
else
|
||||
{
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Routing
|
||||
|
||||
Rules:
|
||||
|
||||
- URL maps to a physical file on disk
|
||||
- Razor paged needs a root folder (Default "Pages")
|
||||
- file extension not included in URL
|
||||
- `Index.cshtml` is entry-point and default document (missing file in URL redirects to index)
|
||||
|
||||
| URL | Maps TO |
|
||||
|------------------------|----------------------------------------------------|
|
||||
| www.domain.com | /Pages/Index.cshtml |
|
||||
| www.domain.com/Index | /Pages/Index.html |
|
||||
| www.domain.com/Account | /Pages/Account.cshtml, /Pages/Account/Index.cshtml |
|
||||
|
||||
## Data Validation
|
||||
|
||||
### Model Annotations
|
||||
|
||||
In `Entity.cs`:
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
|
||||
namespace App.Models
|
||||
{
|
||||
public class Entity
|
||||
{
|
||||
[DisplayName("Integer Number")]
|
||||
[Required]
|
||||
[Range(1, int.MaxValue, ErrorMessage = "Error Message")]
|
||||
public int IntProp { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Tag Helpers & Client Side Validation
|
||||
|
||||
In `View.cshtml`;
|
||||
|
||||
```cs
|
||||
<form method="post" asp-action="Create">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-4">
|
||||
<label asp-for="IntProp"></label>
|
||||
</div>
|
||||
|
||||
<div class="col-8">
|
||||
<input asp-for="IntProp" class="form-control"/>
|
||||
<span asp-validation-for="IntProp" class="text-danger"></span> // error message displayed here
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
// client side validation
|
||||
@section Scripts{
|
||||
@{ <partial name="_ValidationScriptsPartial" /> }
|
||||
}
|
||||
```
|
||||
|
||||
### Server Side Validation
|
||||
|
||||
```cs
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using App.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace App.Controllers
|
||||
{
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
private readonly ApplicationDbContext _db;
|
||||
|
||||
// get db context through dependency injection
|
||||
public IndexModel(AppDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[BindProperty]
|
||||
public Entity Entity { get; set; }
|
||||
|
||||
public async Task OnGet(int id)
|
||||
{
|
||||
Entity = await _db.Entities.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost()
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return RedirectToPage("Index");
|
||||
}
|
||||
else
|
||||
{
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
53
dotnet/asp.net/rest-api.md
Normal file
53
dotnet/asp.net/rest-api.md
Normal file
|
@ -0,0 +1,53 @@
|
|||
# ASP.NET REST API
|
||||
|
||||
```cs
|
||||
[Route("api/endpoint")]
|
||||
[ApiController]
|
||||
public class EntitiesController : ControllerBase // API controller
|
||||
{
|
||||
private readonly IEntityService _service;
|
||||
|
||||
public EntitiesController(IEntityService service, IMapper mapper)
|
||||
{
|
||||
_service = service;
|
||||
_mapper = mapper
|
||||
}
|
||||
|
||||
[HttpGet] // GET api/endpoint
|
||||
public ActionResult<IEnumerable<EntityDTO>> GetEntities()
|
||||
{
|
||||
IEnumerable<EntityDTO> results = /* ... */
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
[HttpGet("{id}")] // GET api/endpoint/{id}
|
||||
public ActionResult<EntityDTO> GetEntityById(int id)
|
||||
{
|
||||
var result = /* .. */;
|
||||
|
||||
if(result != null)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[HttpPost] // POST api/endpoint
|
||||
public ActionResult<EntityDTO> CreateEntity([FromBody] EntityDTO entity)
|
||||
{
|
||||
// persist the entity
|
||||
|
||||
var id = /* ID of the created entity */
|
||||
return Created(id, entity);
|
||||
}
|
||||
|
||||
[HttpPut] // PUT api/endpoint
|
||||
public ActionResult<EntityDTO> UpdateEntity([FromBody] EntityDTO entity)
|
||||
{
|
||||
// persist the updated entity
|
||||
|
||||
return Created(uri, entity);
|
||||
}
|
||||
}
|
||||
```
|
201
dotnet/asp.net/signal-r.md
Normal file
201
dotnet/asp.net/signal-r.md
Normal file
|
@ -0,0 +1,201 @@
|
|||
# SignalR
|
||||
|
||||
The SignalR Hubs API enables to call methods on connected clients from the server. In the server code, define methods that are called by client. In the client code, define methods that are called from the server. SignalR takes care of everything behind the scenes that makes real-time client-to-server and server-to-client communications possible.
|
||||
|
||||
## Server-Side
|
||||
|
||||
### Configuration
|
||||
|
||||
In `Startup.cs`:
|
||||
|
||||
```cs
|
||||
namespace App
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the DI container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSignalR();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapHub("/hub/endpoint");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Creating Hubs
|
||||
|
||||
```cs
|
||||
public class CustomHub : Hub
|
||||
{
|
||||
public task HubMethod(Type args)
|
||||
{
|
||||
// trigger function on all clients and pass args to it
|
||||
return Clients.All.SendAsync("CLientMethod", args);
|
||||
|
||||
// trigger function on caller client and pass args to it
|
||||
return Clients.Caller.SendAsync("CLientMethod", args);
|
||||
|
||||
// trigger function on clients of a group and pass args to it
|
||||
return Clients.Group("GroupName").SendAsync("CLientMethod", args);
|
||||
|
||||
// other operations
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Strongly Typed Hubs
|
||||
|
||||
A drawback of using `SendAsync` is that it relies on a magic string to specify the client method to be called. This leaves code open to runtime errors if the method name is misspelled or missing from the client.
|
||||
|
||||
An alternative to using SendAsync is to strongly type the Hub with `Hub<T>`.
|
||||
|
||||
```cs
|
||||
public interface IHubClient
|
||||
{
|
||||
// matches method to be called on the client
|
||||
Task ClientMethod(Type args);
|
||||
}
|
||||
```
|
||||
|
||||
```cs
|
||||
public class CustomHub : Hub<IHubClient>
|
||||
{
|
||||
public Task HubMethod(Type args)
|
||||
{
|
||||
return Clients.All.ClientMethod(args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Using `Hub<T>` enables compile-time checking of the client methods. This prevents issues caused by using magic strings, since `Hub<T>` can only provide access to the methods defined in the interface.
|
||||
|
||||
Using a strongly typed `Hub<T>` disables the ability to use `SendAsync`. Any methods defined on the interface can still be defined as asynchronous. In fact, each of these methods should return a `Task`. Since it's an interface, don't use the `async` keyword.
|
||||
|
||||
### Handling Connection Events
|
||||
|
||||
The SignalR Hubs API provides the OnConnectedAsync and OnDisconnectedAsync virtual methods to manage and track connections. Override the OnConnectedAsync virtual method to perform actions when a client connects to the Hub, such as adding it to a group.
|
||||
|
||||
```cs
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, "GroupName");
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "GroupName");
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
```
|
||||
|
||||
Override the `OnDisconnectedAsync` virtual method to perform actions when a client disconnects.
|
||||
If the client disconnects intentionally (by calling `connection.stop()`, for example), the exception parameter will be null.
|
||||
However, if the client is disconnected due to an error (such as a network failure), the exception parameter will contain an exception describing the failure.
|
||||
|
||||
### Sending Errors to the client
|
||||
|
||||
Exceptions thrown in the hub methods are sent to the client that invoked the method. On the JavaScript client, the `invoke` method returns a JavaScript Promise. When the client receives an error with a handler attached to the promise using catch, it's invoked and passed as a JavaScript `Error` object.
|
||||
|
||||
If the Hub throws an exception, connections aren't closed. By default, SignalR returns a generic error message to the client.
|
||||
|
||||
If you have an exceptional condition you *do* want to propagate to the client, use the `HubException` class. If you throw a `HubException` from your hub method, SignalR will send the entire message to the client, unmodified.
|
||||
|
||||
```cs
|
||||
public Task ThrowException()
|
||||
{
|
||||
throw new HubException("This error will be sent to the client!");
|
||||
}
|
||||
```
|
||||
|
||||
### Client-Side (JavaScript)
|
||||
|
||||
### Installing the client package
|
||||
|
||||
```sh
|
||||
npm init -y
|
||||
npm install @microsoft/signalr
|
||||
```
|
||||
|
||||
npm installs the package contents in the `node_modules\@microsoft\signalr\dist\browser` folder. Create a new folder named signalr under the `wwwroot\lib` folder. Copy the signalr.js file to the `wwwroot\lib\signalr` folder.
|
||||
|
||||
Reference the SignalR JavaScript client in the `<script>` element. For example:
|
||||
|
||||
```html
|
||||
<script src="~/lib/signalr/signalr.js"></script>
|
||||
```
|
||||
|
||||
### Connecting to a Hub
|
||||
|
||||
[Reconnect Clients Docs](https://docs.microsoft.com/en-us/aspnet/core/signalr/javascript-client#reconnect-clients)
|
||||
|
||||
```js
|
||||
const connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl("/hub/endpoint")
|
||||
.configureLogging(signalR.LogLevel.Information)
|
||||
.withAutomaticReconnect() // optional
|
||||
.build();
|
||||
|
||||
// async/await connection start
|
||||
async function connect() {
|
||||
try {
|
||||
await connection.start();
|
||||
console.log("SignalR Connected.");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
// promise connection start
|
||||
function connect() {
|
||||
connection.start()
|
||||
.then(() => {})
|
||||
.catch((err) => {console.error(err)});
|
||||
}
|
||||
```
|
||||
|
||||
### Call hub methods fom the client
|
||||
|
||||
JavaScript clients call public methods on hubs via the `invoke` method of the `HubConnection`. The `invoke` method accepts:
|
||||
|
||||
- The name of the hub method.
|
||||
- Any arguments defined in the hub method.
|
||||
|
||||
```js
|
||||
try {
|
||||
await connection.invoke("HubMethod", args);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
```
|
||||
|
||||
The `invoke` method returns a JavaScript `Promise`. The `Promise` is resolved with the return value (if any) when the method on the server returns. If the method on the server throws an error, the `Promise` is rejected with the error message. Use `async` and `await` or the `Promise`'s then and catch methods to handle these cases.
|
||||
|
||||
JavaScript clients can also call public methods on hubs via the the `send` method of the `HubConnection`. Unlike the `invoke` method, the send method doesn't wait for a response from the server. The send method returns a JavaScript `Promise`. The `Promise` is resolved when the message has been sent to the server. If there is an error sending the message, the `Promise` is rejected with the error message. Use `async` and `await` or the `Promise`'s then and catch methods to handle these cases.
|
||||
|
||||
### Call client methods from the hub
|
||||
|
||||
To receive messages from the hub, define a method using the `on` method of the `HubConnection`. The `on` method accepts:
|
||||
|
||||
- The name of the JavaScript client method.
|
||||
- Arguments the hub passes to the method.
|
||||
|
||||
```cs
|
||||
connection.on("ClientMethod", (args) => { /* ... */});
|
||||
```
|
85
dotnet/asp.net/web-forms.md
Normal file
85
dotnet/asp.net/web-forms.md
Normal file
|
@ -0,0 +1,85 @@
|
|||
# WebForms
|
||||
|
||||
## `Page.aspx`
|
||||
|
||||
The fist loaded page is `Default.aspx` and its underlying code.
|
||||
|
||||
```html
|
||||
<!-- directive -->
|
||||
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Project.Default" %>
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"> <!-- XML Namespace -->
|
||||
<head runat="server"> <!-- runat: handle as ASP code -->
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- web forms require a form tag to be the whole body -->
|
||||
<form id="form1" runat="server"> <!-- runat: handle as ASP code -->
|
||||
<div>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Page Directive
|
||||
|
||||
```cs
|
||||
<%@ Page Language="C#" // define language used (can be C# or VB)
|
||||
AutoEventWireup="true" // automatically create and setup event handlers
|
||||
CodeBehind="Default.aspx.cs" // define the underlying code file
|
||||
Inherits="EmptyWebForm.Default" %>
|
||||
```
|
||||
|
||||
### Web Controls
|
||||
|
||||
```xml
|
||||
<asp:Control ID="" runat="server" ...></asp:Control>
|
||||
|
||||
<!-- Label: empty text will diplay ID, use empty space as text for empty label -->
|
||||
<asp:Label ID="lbl_" runat="server" Text=" "></asp:Label>
|
||||
<!-- TextBox -->
|
||||
<asp:TextBox ID="txt_" runat="server"></asp:TextBox>
|
||||
<!-- Button -->
|
||||
<asp:Button ID="btn_" runat="server" Text="ButtonText" OnClick="btn_Click" />
|
||||
<!-- HyperLink -->
|
||||
<asp:HyperLink ID="lnk_" runat="server" NavigateUrl="~/Page.aspx">LINK TEXT</asp:HyperLink>
|
||||
<!-- LinkButton: POstBackEvent reloads the page -->
|
||||
<asp:LinkButton ID="lbtHome" runat="server" PostBackUrl="~/Page.aspx" OnClick="lbt_Click">BUTTON TEXT</asp:LinkButton>
|
||||
<!-- Image -->
|
||||
<asp:Image ID="img_" runat="server" ImageUrl="~/Images/image.png"/>
|
||||
<!-- ImageButton -->
|
||||
<asp:ImageButton ID="imb_" runat="server" ImageUrl="~/Images/image.png" PostBackUrl="~/Page.aspx"/>
|
||||
|
||||
<!-- SqlSataSource; connection string specified in Web.config -->
|
||||
<asp:SqlDataSource ID="sds_" runat="server" ConnectionString="<%$ ConnectionStrings:ConnectionString %>" SelectCommand="SQL Query"></asp:SqlDataSource>
|
||||
```
|
||||
|
||||
## `Page.aspx.cs`
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.UI;
|
||||
using System.Web.UI.WebControls;
|
||||
|
||||
namespace Project
|
||||
{
|
||||
public partial class Default : System.Web.UI.Page
|
||||
{
|
||||
protected void Page_Load(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected void Control_Event(object sender, EventArgs e)
|
||||
{
|
||||
// actions on event trigger
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
113
dotnet/database/ado.net.md
Normal file
113
dotnet/database/ado.net.md
Normal file
|
@ -0,0 +1,113 @@
|
|||
# [ADO.NET](https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/ "ADO.NET Docs")
|
||||
|
||||
`ADO.NET` is a set of classes that expose data access services for .NET.
|
||||
The `ADO.NET` classes are found in `System.Data.dll`, and are integrated with the XML classes found in `System.Xml.dll`.
|
||||
|
||||
[ADO.NET provider for SQLite](https://system.data.sqlite.org/index.html/doc/trunk/www/index.wiki "System.Data.SQLite")
|
||||
|
||||
## [Connection Strings](https://www.connectionstrings.com)
|
||||
|
||||
### [SQL Server 2019](https://www.connectionstrings.com/sql-server-2019/)
|
||||
|
||||
- Standard Security:
|
||||
- `Server=<server_name>; Database=<database>; UID=<user>; Pwd=<password>;`
|
||||
- `Server=<server_name>; Database=<database>; User ID=<user>; Password=<password>;`
|
||||
- `Data Source=<server_name>; Initial Catalog=<database>; UID=<user>; Pwd=<password>;`
|
||||
- Specific Instance: `Server=<server_name>\<instance_name>; Database=<database>; User ID=<user>; Password=<password>;`
|
||||
- Trusted Connection (WinAuth): `Server=<server_name>; Database=<database>; Trusted_Connection=True;`
|
||||
- MARS: `Server=<server_name>; Database=<database>; Trusted_Connection=True; MultipleActiveResultSets=True;`
|
||||
|
||||
**NOTE**: *Multiple Active Result Sets* (MARS) is a feature that works with SQL Server to allow the execution of multiple batches on a single connection.
|
||||
|
||||
### [SQLite](https://www.connectionstrings.com/sqlite/)
|
||||
|
||||
- Basic: `Data Source: path\to\db.sqlite3; Version=3;`
|
||||
- In-Memory Database: `Data Source=:memory:; Version=3; New=True`
|
||||
- With Password: `Data Source: path\to\db.sqlite3; Version=3; Password=<password>`
|
||||
|
||||
## Connection to DB
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using System.Data.SqlClient; // ADO.NET Provider, installed through NuGet
|
||||
|
||||
namespace <namespace>
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// Connection to SQL Server DBMS
|
||||
SqlConnectionStringBuilder connectionString = new SqlConnectionStringBuilder();
|
||||
connectionString.DataSource = "<server_name>";
|
||||
connectionString.UserID = "<user>";
|
||||
connectionString.Password = "<password>";
|
||||
connectionString.InitialCatalog = "<database>";
|
||||
|
||||
// more compact
|
||||
SqlConnectionStringBuilder connectionString = new SqlConnectionStringBuilder("Server=<server_name>;Database=<database>;UID=<user>;Pwd=<password>")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## DB Interrogation
|
||||
|
||||
### `SqlConnection`
|
||||
|
||||
```cs
|
||||
using (SqlConnection connection = new SqlConnection())
|
||||
{
|
||||
connection.ConnectionString = connectionString.ConnectionString;
|
||||
connection.Open(); // start communication w/ sql server
|
||||
}
|
||||
|
||||
// more compact
|
||||
using (SqlConnection connection = new SqlConnection(connectionString)) {
|
||||
connection.Open()
|
||||
}
|
||||
```
|
||||
|
||||
### [SqlCommand](https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand)
|
||||
|
||||
```cs
|
||||
string sql = "<sql_instruction>";
|
||||
|
||||
using (SqlCommand command = new SqlCommand())
|
||||
{
|
||||
command.Connection = connection; // SqlConnection
|
||||
command.CommandText = "... @Parameter"; // or name of StoredProcedure
|
||||
|
||||
// add parameters to the SqlParameterCollection, WARNING: table names or columns cannot be parameters
|
||||
command.Parameters.Add("@Parameter", SqlDbType.<DBType>, columnLength).Value = value;
|
||||
command.Parameters.AddWithValue("@Parameter", value);
|
||||
command.Parameters.AddWithValue("@Parameter", (object) value ?? DBNull.Value); // if Parameter is nullable
|
||||
|
||||
// Create an instance of a SqlParameter object.
|
||||
command.CreateParameter();
|
||||
|
||||
command.CommandType = CommandType.Text; // or StoredProcedure
|
||||
|
||||
int affectedRows = command.ExecuteNonQuery(); // execute the query and return the number of affected rows
|
||||
}
|
||||
```
|
||||
|
||||
### `SqlDataReader`
|
||||
|
||||
```cs
|
||||
using (SqlDataReader cursor = command.ExecuteReader()) // object to get data from db
|
||||
{
|
||||
while (cursor.Read()) // get data till possible
|
||||
{
|
||||
// preferred methodology
|
||||
cursor["<column_name>"].ToString(); // retrieve data form the column
|
||||
cursor[<column_index>].ToString(); // retrieve data form the column
|
||||
|
||||
// check for null before retrieving the value
|
||||
if(!cursor.IsDBNull(n))
|
||||
{
|
||||
cursor.Get<SystemType>(index); // retrieve data form the n-th column
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
125
dotnet/database/entity-framework.md
Normal file
125
dotnet/database/entity-framework.md
Normal file
|
@ -0,0 +1,125 @@
|
|||
# Entity Framework
|
||||
|
||||
## Model & Data Annotations
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
|
||||
namespace <Project>.Model
|
||||
{
|
||||
public class Entity
|
||||
{
|
||||
[Key] // set as PK (Id & EntityId are automatically detected to be PKs)
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public Type ForeignObject { get; set; } // Not Null in DB
|
||||
public Type ForeignObject { get; set; } // Allow Null in DB
|
||||
|
||||
public int Prop { get; set; } // Not Null in DB (primitive are not nullable)
|
||||
public int? Prop { get; set; } // Allow Null in DB
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
NuGet Packages to install:
|
||||
|
||||
- `Microsoft.EntityFrameworkCore`
|
||||
- `Microsoft.EntityFrameworkCore.Tools` to use migrations in Visual Studio
|
||||
- `Microsoft.EntityFrameworkCore.Tools.DotNet` to use migrations in `dotnet` cli (`dotnet-ef`)
|
||||
- `Microsoft.EntityFrameworkCore.Design` *or* `Microsoft.EntityFrameworkCore.<db_provider>.Design` needed for tools to work (bundled w\ tools)
|
||||
- `Microsoft.EntityFrameworkCore.<db_provider>`
|
||||
|
||||
```cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace <Project>.Model
|
||||
{
|
||||
class Context : DbContext
|
||||
{
|
||||
private const string _connectionString = "Server=<server_name>;Database=<database>;UID=<user>;Pwd=<password>";
|
||||
|
||||
// connect to db
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseSqlServer(_connectionString); // specify connection
|
||||
}
|
||||
|
||||
// or
|
||||
|
||||
public Context(DbContextOptions options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
//DBSet<TEntity> represents the collection of all entities in the context (or that can be queried from the database) of a given type
|
||||
public DbSet<Entity> Entities { get; set; }
|
||||
public DbSet<Entity> Entities => Set<Entity>(); // with nullable reference types
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Migrations
|
||||
|
||||
Create & Update DB Schema if necessary.
|
||||
|
||||
In Package Manager Shell:
|
||||
|
||||
```ps1
|
||||
PM> Add-Migration <migration_name>
|
||||
PM> update-database [-Verbose] # use the migrations to modify the db, -Verbose to show SQL queries
|
||||
```
|
||||
|
||||
In dotnet cli:
|
||||
|
||||
```ps1
|
||||
dotnet tool install --global dotnet-ef # if not already installed
|
||||
|
||||
dotnet ef migrations add <migration_name>
|
||||
dotnet ef database update
|
||||
```
|
||||
|
||||
## CRUD
|
||||
|
||||
### Create
|
||||
|
||||
```cs
|
||||
context.Add(entity);
|
||||
context.AddRange(entities);
|
||||
|
||||
context.SaveChanges();
|
||||
```
|
||||
|
||||
### Read
|
||||
|
||||
[Referenced Object Not Loading Fix](https://stackoverflow.com/a/5385288)
|
||||
|
||||
```cs
|
||||
context.Entities.ToList();
|
||||
context.Entities.Find(id);
|
||||
|
||||
// force read of foreign key identifying referenced obj
|
||||
context.Entities.Include(c => c.ForeignObject).Find(id);
|
||||
```
|
||||
|
||||
### Update
|
||||
|
||||
```cs
|
||||
context.Entities.Update(entity);
|
||||
context.UpdateRange(entities);
|
||||
|
||||
context.SaveChanges();
|
||||
```
|
||||
|
||||
### Delete
|
||||
|
||||
```cs
|
||||
context.Entities.Remove(entity);
|
||||
context.RemoveRange(entities);
|
||||
|
||||
context.SaveChanges();
|
||||
```
|
140
dotnet/godot/scripting.md
Normal file
140
dotnet/godot/scripting.md
Normal file
|
@ -0,0 +1,140 @@
|
|||
# Godot Scripting
|
||||
|
||||
## Basics
|
||||
|
||||
```cs
|
||||
using Godot;
|
||||
|
||||
|
||||
public class NodeName : NodeType
|
||||
{
|
||||
[Export] // make variable visible in inspector
|
||||
Type variable = value;
|
||||
|
||||
|
||||
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
public override void _Ready()
|
||||
{
|
||||
GetNode("NodeName"); // fetch a child node in the scene
|
||||
GetNode("ParentNode/ChildNode"); // fetch a child node in the scene
|
||||
|
||||
AddToGroup("Group"); // add a node to a group (similar to tags)
|
||||
|
||||
GetTree().CallGroup("Group", "Function"); // call Function on all group members
|
||||
var groupMembers = GetTree().GetNodesInGroup("Group");
|
||||
}
|
||||
|
||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
public override void _Process(float delta)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void _OnEmitterSignal() { }
|
||||
}
|
||||
```
|
||||
|
||||
### Overridable Functions
|
||||
|
||||
```cs
|
||||
public override void _EnterTree()
|
||||
{
|
||||
// When the node enters the Scene Tree, it becomes active and this function is called.
|
||||
// Children nodes have not entered the active scene yet.
|
||||
// In general, it's better to use _ready() for most cases.
|
||||
base._EnterTree();
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// This function is called after _enter_tree, but it ensures
|
||||
// that all children nodes have also entered the Scene Tree,
|
||||
// and became active.
|
||||
base._Ready();
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
// When the node exits the Scene Tree, this function is called.
|
||||
// Children nodes have all exited the Scene Tree at this point and all became inactive.
|
||||
base._ExitTree();
|
||||
}
|
||||
|
||||
public override void _Process(float delta)
|
||||
{
|
||||
// This function is called every frame.
|
||||
base._Process(delta);
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
{
|
||||
// This is called every physics frame.
|
||||
base._PhysicsProcess(delta);
|
||||
}
|
||||
```
|
||||
|
||||
### Creating Nodes
|
||||
|
||||
```cs
|
||||
private Sprite _sprite;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
|
||||
_sprite = new Sprite(); // Create a new sprite
|
||||
|
||||
AddChild(_sprite); // Add it as a child of this node
|
||||
_sprite.Free(); // Immediately removes the node from the scene and frees it.
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: When a node is freed, it also frees all its child nodes.
|
||||
|
||||
The safest way to delete a node is by using `Node.QueueFree()`. This erases the node safely during idle.
|
||||
|
||||
### Instantiating Scenes
|
||||
|
||||
```cs
|
||||
// STEP 1: load the scene
|
||||
var scene = GD.Load<PackedScene>("res://scene.tscn"); // Will load when the script is instanced.
|
||||
|
||||
// STEP 2: instantiate the scene-node
|
||||
var node = scene.Instance();
|
||||
AddChild(node);
|
||||
```
|
||||
|
||||
The advantage of this two-step process is that a packed scene may be kept loaded and ready to use so that it's possible to create as many instances as desired.
|
||||
This is especially useful to quickly instance several enemies, bullets, and other entities in the active scene.
|
||||
|
||||
## Signals
|
||||
|
||||
Signals are Godot's version of the *observer* pattern. They allow a node to send out a message that other nodes can listen for and respond to.
|
||||
|
||||
Signals are a way to decouple game objects, which leads to better organized and more manageable code. Instead of forcing game objects to expect other objects to always be present, they can instead emit signals that all interested objects can subscribe to and respond to.
|
||||
|
||||
```cs
|
||||
public override _Ready()
|
||||
{
|
||||
GetNode("Node").Connect("signal", targetNode, nameof(TargetFunction)); // connect node and signal
|
||||
}
|
||||
|
||||
// Signal Handler
|
||||
public void OnEmitterSignal() { }
|
||||
```
|
||||
|
||||
### Custom Signals
|
||||
|
||||
```cs
|
||||
public class Node : Node2D
|
||||
{
|
||||
[Signal]
|
||||
public delegate void CustomSignal(Type arg, ...);
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
EmitSignal(nameof(CustomSignal), args);
|
||||
}
|
||||
}
|
||||
```
|
35
dotnet/lib/skia-sharp.md
Normal file
35
dotnet/lib/skia-sharp.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# SkiaSharp
|
||||
|
||||
```cs
|
||||
SKImageInfo info = new SKImageInfo(width, height);
|
||||
|
||||
using (SKSurface surface = SKSurface.Create(info))
|
||||
{
|
||||
SKCanvas canvas = surface.Canvas;
|
||||
canvas.DrawColor(SKColors.<color>);
|
||||
|
||||
using (SKPaint paint = new SKPaint())
|
||||
{
|
||||
paint.Color = SKColors.Blue;
|
||||
paint.IsAntialias = true;
|
||||
paint.StrokeWidth = 5;
|
||||
paint.Style = SKPaintStyle.Stroke;
|
||||
|
||||
// draw
|
||||
canvas.DrawLine(x0, y0, x1, y1, paint);
|
||||
|
||||
}
|
||||
|
||||
// save to file
|
||||
using (var image = surface.Snapshot())
|
||||
{
|
||||
using (var data = image.Encode(SKEncodedImageFormat.Png, 100))
|
||||
{
|
||||
using (var stream = File.OpenWrite(png_filename))
|
||||
{
|
||||
data.SaveTo(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
81
dotnet/unity/collisions.md
Normal file
81
dotnet/unity/collisions.md
Normal file
|
@ -0,0 +1,81 @@
|
|||
# Collisions (Physics)
|
||||
|
||||
## Rigidbody Component
|
||||
|
||||
Enables physics on the game objects.
|
||||
|
||||
Rigidbodies collide with other objects instead of going through them.
|
||||
|
||||
Avoid object rotation on collisions:
|
||||
|
||||
1. Assign `Rigidbody` component to object
|
||||
2. Enable Freeze Rotation in Rigidbody > Constraints
|
||||
|
||||
```cs
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
public class GameObject : MonoBehaviour {
|
||||
|
||||
Rigidbody = rigidbody; // game object rigidbody reference container
|
||||
|
||||
void Start()
|
||||
{
|
||||
rigidbody = GetComponent<Rigidbody>(); // get rigidbody reference
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
}
|
||||
|
||||
// FixedUpdate is calls every x seconds (not influenced by FPS instability)
|
||||
// used for physics calculations which should be FPS independent
|
||||
void FixedUpdate()
|
||||
{
|
||||
Time.fixedDeltaTime; // fixed amount of time
|
||||
Time.timeDelta; // if called inside FIxedUpdate() behaves like fixedDeltaTime
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Box Collider Component
|
||||
|
||||
Enable `Is Trigger` to register the collision but avoid blocking the movement of the objects.
|
||||
The trigger can generate a event to signal the contact with the object.
|
||||
|
||||
One of the colliding GameObjects *must have* the `Rigidbody` component and the other `Is Trigger` enabled.
|
||||
To detect the collision but avoid computing the physics `Is Kinematic` must be enabled in the `Rigidbody` component.
|
||||
|
||||
```cs
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
public class GameObject : MonoBehaviour {
|
||||
|
||||
Rigidbody = rigidbody; // game object rigidbody reference container
|
||||
|
||||
void Start()
|
||||
{
|
||||
rigidbody = GetComponent<Rigidbody>(); // get rigidbody reference
|
||||
}
|
||||
|
||||
// FixedUpdate is calls every x seconds (not influenced by FPS instability)
|
||||
// used for physics calculations which should be FPS independent
|
||||
void FixedUpdate()
|
||||
{
|
||||
Time.fixedDeltaTime; // fixed amount of time
|
||||
Time.timeDelta; // if called inside FixedUpdate() behaves like fixedDeltaTime
|
||||
}
|
||||
|
||||
// called on box collision.
|
||||
void OnTriggerEnter(Collider triggerCollider) {
|
||||
|
||||
// detect a collision with a particular GameObject(must have a TAG)
|
||||
if (triggerCollider.tag = "tag") {
|
||||
Destroy(triggerCollider.gameObject); // destroy tagged item on collision
|
||||
//or
|
||||
Destroy(gameObject); // destroy itself
|
||||
}
|
||||
}
|
||||
```
|
30
dotnet/unity/coroutines.md
Normal file
30
dotnet/unity/coroutines.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Coroutines
|
||||
|
||||
[Coroutines - Unity manual](https://docs.unity3d.com/Manual/Coroutines.html)
|
||||
|
||||
When you call a function, it runs to completion before returning. This effectively means that any action taking place in a function must happen *within a single frame update*; a function call can't be used to contain a procedural animation or a sequence of events over time.
|
||||
|
||||
A coroutine is like a function that has the ability to pause execution and return control to Unity but then to continue where it left off on the following frame.
|
||||
|
||||
It is essentially a function declared with a return type of IEnumerator and with the yield return statement included somewhere in the body. The `yield return null` line is the point at which execution will pause and be resumed the following frame.
|
||||
|
||||
```cs
|
||||
//coroutine
|
||||
IEnumerator coroutine()
|
||||
{
|
||||
// action performed
|
||||
yield return null; // pause until next iteration
|
||||
|
||||
// or
|
||||
|
||||
// By default, a coroutine is resumed on the frame after it yields but it is also possible to introduce a time delay
|
||||
yield return new WaitForSeconds(seconds); // wait seconds before resuming
|
||||
|
||||
// or
|
||||
|
||||
yeld return StartCoroutine(coroutine()); // wait for another coroutine to finish before starting
|
||||
}
|
||||
|
||||
StartCoroutine(coroutine()); // start the coroutine
|
||||
StopCoroutine(coroutine()); // stop the coroutine
|
||||
```
|
81
dotnet/unity/input-manager.md
Normal file
81
dotnet/unity/input-manager.md
Normal file
|
@ -0,0 +1,81 @@
|
|||
# Input Manager
|
||||
|
||||
The Input Manager uses the following types of controls:
|
||||
|
||||
- **Key** refers to any key on a physical keyboard, such as `W`, `Shift`, or the `space bar`.
|
||||
- **Button** refers to any button on a physical controller (for example, gamepads), such as the `X` button on an Xbox One controller.
|
||||
- A **virtual axis** (plural: axes) is mapped to a **control**, such as a button or a key. When the user activates the control, the axis receives a value in the range of `[-1..1]`.
|
||||
|
||||
## Virtual axes
|
||||
|
||||
### Axis Properties
|
||||
|
||||
**Name**: Axis name. You can use this to access the axis from scripts.
|
||||
**Negative Button**, **Positive Button**: The controls to push the axis in the negative and positive direction respectively. These can be keys on a keyboard, or buttons on a joystick or mouse.
|
||||
**Alt Negative Button**, **Alt Positive Button**: Alternative controls to push the axis in the negative and positive direction respectively.
|
||||
**Gravity**: Speed in units per second that the axis falls toward neutral when no input is present.
|
||||
**Dead**: How far the user needs to move an analog stick before your application registers the movement. At runtime, input from all analog devices that falls within this range will be considered null.
|
||||
**Sensitivity**: Speed in units per second that the axis will move toward the target value. This is for digital devices only.
|
||||
**Snap**: If enabled, the axis value will reset to zero when pressing a button that corresponds to the opposite direction.
|
||||
**Type**: The type of input that controls the axis. Select from these values:
|
||||
|
||||
- Key or Mouse button
|
||||
- Mouse Movement
|
||||
- Joystick Axis
|
||||
|
||||
**Axis**: The axis of a connected device that controls this axis.
|
||||
**JoyNum**: The connected Joystick that controls this axis. You can select a specific joystick, or query input from all joysticks.
|
||||
|
||||
### Axis Values
|
||||
|
||||
Axis values can be:
|
||||
|
||||
- Between `-1` and `1` for joystick and keyboard input. The neutral position for these axes is `0`. Some types of controls, such as buttons on a keyboard, aren't sensitive to input intensity, so they can't produce values other than `-1`, `0`, or `1`.
|
||||
- Mouse delta (how much the mouse has moved during the last frame) for mouse input. The values for mouse input axes can be larger than `1` or smaller than `-1` when the user moves the mouse quickly.
|
||||
|
||||
```cs
|
||||
//Define the speed at which the object moves.
|
||||
float moveSpeed = 10;
|
||||
|
||||
//Get the value of the Horizontal input axis.
|
||||
float horizontalInput = Input.GetAxis("Horizontal");
|
||||
|
||||
//Get the value of the Vertical input axis.
|
||||
float verticalInput = Input.GetAxis("Vertical");
|
||||
|
||||
Vector3 direction = new Vector3(horizontalInput, 0, verticalInput).normalized;
|
||||
Vector3 velocity = direction * moveSpeed;
|
||||
|
||||
//Move the object to XYZ coordinates defined as horizontalInput, 0, and verticalInput respectively.
|
||||
transform.Translate(velocity * Time.deltaTime);
|
||||
```
|
||||
|
||||
[Time.deltaTime][dt] represents the time that passed since the last frame. Multiplying the moveSpeed variable by Time.deltaTime ensures that the GameObject moves at a constant speed every frame.
|
||||
|
||||
[dt]: https://docs.unity3d.com/ScriptReference/Time-deltaTime.html
|
||||
|
||||
`GetAxis`: returns the value of the virtual axis identified.
|
||||
`GetAxisRaw`: returns the value of the virtual axis identified with no smoothing filtering applied.
|
||||
|
||||
## Keys
|
||||
|
||||
**Letter keys**: `a`, `b`, `c`, ...
|
||||
**Number keys**: `1`, `2`, `3`, ...
|
||||
**Arrow keys**: `up`, `down`, `left`, `right`
|
||||
**Numpad keys**: `[1]`, `[2]`, `[3]`, `[+]`, `[equals]`, ...
|
||||
**Modifier keys**: `right shift`, `left shift`, `right ctrl`, `left ctrl`, `right alt`, `left alt`, `right cmd`, `left cmd`
|
||||
**Special keys**: `backspace`, `tab`, `return`, `escape`, `space`, `delete`, `enter`, `insert`, `home`, `end`, `page up`, `page down`
|
||||
**Function keys**: `f1`, `f2`, `f3`, ...
|
||||
|
||||
**Mouse buttons**: `mouse 0`, `mouse 1`, `mouse 2`, ...
|
||||
|
||||
**Specific button on *any* joystick**: `joystick button 0`, `joystick button 1`, `joystick button 2`, ...
|
||||
**specific button on a *specific* joystick**: `joystick 1 button 0`, `joystick 1 button 1`, `joystick 2 button 0`, ...
|
||||
|
||||
```cs
|
||||
Input.GetKey("a");
|
||||
Input.GetKey(KeyCode.A);
|
||||
```
|
||||
|
||||
[Input.GetKey](https://docs.unity3d.com/ScriptReference/Input.GetKey.html)
|
||||
[KeyCode](https://docs.unity3d.com/ScriptReference/KeyCode.html)
|
14
dotnet/unity/prefabs-instantiation.md
Normal file
14
dotnet/unity/prefabs-instantiation.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Prefabs
|
||||
|
||||
Prefabs are a blueprint for GameObjects and any change made to the prefab is inherited by all it's instances.
|
||||
|
||||
## Script Instantiation
|
||||
|
||||
```cs
|
||||
|
||||
public GameObject prefab; // reference to the prefab
|
||||
|
||||
// instantiate a game GameObject from the prefab with specified position and rotation (rotation must be a quaternion)
|
||||
GameObject newInstance = (GameObject) Instantiate(prefab, positionVector3, Quaternion.Euler(rotationVector3)); // instantiate prefab and get reference to instance
|
||||
// Instance returns a object and since a GameObject was passed to Instantiate() the returned object can be casted to a GameObject
|
||||
```
|
83
dotnet/unity/raycasting.md
Normal file
83
dotnet/unity/raycasting.md
Normal file
|
@ -0,0 +1,83 @@
|
|||
# Raycasting Notes
|
||||
|
||||
A raycast is conceptually like a laser beam that is fired from a point in space along a particular direction. Any object making contact with the beam can be detected and reported.
|
||||
|
||||
## 3D Raycasting
|
||||
|
||||
```cs
|
||||
void Update()
|
||||
{
|
||||
// constructor takes in a position end direction
|
||||
Ray ray = new Ray(transform,position, transform.forward);
|
||||
RaycastHit hitInfo; // struct, stores data on ray collision
|
||||
|
||||
hitInfo.distance // distance from origin to collision point
|
||||
hitInfo.collider // collider of the hit object
|
||||
hitInfo.transform // transform og the hit object
|
||||
hitInfo.collider.gameObject // reference to the object hit by the ray
|
||||
hitInfo.collider.gameObject // reference to the object hit by the ray
|
||||
hitInfo.normal // normal vector og the hit surface
|
||||
hitInfo.point // actual point of collision
|
||||
|
||||
// static method, object must have a collider dot the collision to happen, returns a BOOL
|
||||
Physics.Raycast(ray, out hitInfo); // update hitInfo based on ray collisions
|
||||
Physics.Raycast(ray, out hitInfo, float maxRayDistance); // limit the ray length
|
||||
Physics.Raycast(ray, out hitInfo, Mask mask); // specify with which layers the ray can interact, layer must be applied to object's mask
|
||||
Physics.Raycast(ray, out hitInfo, Mask mask, QueryTriggerInteraction.Ignore); // ignore collision if "is trigger" is enabled on other objects
|
||||
|
||||
// detect a collision
|
||||
if (Physics.Raycast(ray, out hitInfo))
|
||||
{
|
||||
//collision happened
|
||||
|
||||
// draw the ray in game for debugging
|
||||
Debug.DrawLine(ray.origin, hitInfo.point, Color.red); // draw red line if collision happens
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.DrawLine(ray.origin, ray.origin + ray.direction * 100, Color.blue); // draw blue line if collision happens, arrival point is 100 units from the origin since the ray goes to infinity
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Detect mouse pointed point in-game
|
||||
|
||||
```cs
|
||||
public Camera gameCamera;
|
||||
|
||||
void Update()
|
||||
{
|
||||
// ray going from camera through a screen point
|
||||
Ray ray = gameCamera.ScreenPointToRay(Input.mousePosition); // Input.mousePosition is the position of the mouse in pixels (screen points)
|
||||
RaycastHit hitInfo; // place pointed by the mouse
|
||||
|
||||
Physics.Raycast(ray, out hitInfo) // update pointed position
|
||||
}
|
||||
```
|
||||
|
||||
## 2D Raycasting
|
||||
|
||||
```cs
|
||||
|
||||
void Start()
|
||||
{
|
||||
Physics2D.queriesStartColliders = false; // avoid collision with collider of the ray generator gameObject
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// returns a RaycastHit2D, needs an origin and direction separately
|
||||
Raycast2D hitInfo = Physics2D.Raycast(Vector2 origin, Vector2 direction);
|
||||
Raycast2D hitInfo = Physics2D.Raycast(Vector2 origin, Vector2 direction, float maxRayDistance);
|
||||
Raycast2D hitInfo = Physics2D.Raycast(Vector2 origin, Vector2 direction, float maxRayDistance);
|
||||
Raycast2D hitInfo = Physics2D.Raycast(Vector2 origin, Vector2 direction, float minDepth, float maxDepth); // set range of z-coord values in which detect hits (sprites depth)
|
||||
|
||||
//! the ray starts from INSIDE the gameObject and can collider with it's collider
|
||||
|
||||
// detect collision
|
||||
if (hitInfo.collider != null) {
|
||||
// collision happened
|
||||
Debug.DrawLine(transform.position, hitInfo.point)
|
||||
}
|
||||
}
|
||||
```
|
135
dotnet/unity/scripting.md
Normal file
135
dotnet/unity/scripting.md
Normal file
|
@ -0,0 +1,135 @@
|
|||
# Unity C# Scripting
|
||||
|
||||
## Logging
|
||||
|
||||
```c#
|
||||
Debug.Log(string); //output message to console (more powerful and flexible than print())
|
||||
Print(string); //output message to console
|
||||
```
|
||||
|
||||
## Scripts
|
||||
|
||||
```c#
|
||||
public class ClassName : MonoBehaviour {
|
||||
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
Time.deltaTime; // time since last frame
|
||||
}
|
||||
|
||||
// FixedUpdate is calls every x seconds (not influenced by FPS instability)
|
||||
// used for physics calculations which should be FPS independent
|
||||
void FixedUpdate()
|
||||
{
|
||||
Time.fixedDeltaTime; // fixed amount of time
|
||||
Time.timeDelta; // if called inside FIxedUpdate() behaves like fixedDeltaTime
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Script communication
|
||||
|
||||
Referencing data in a script from another.
|
||||
|
||||
```cs
|
||||
//example of a script to be referenced in another
|
||||
Using System;
|
||||
|
||||
public class Player : MonoBehaviour {
|
||||
|
||||
public float health = 10;
|
||||
public event Action OnPlayerDeath; //event of type Action, needs using System
|
||||
|
||||
void Start() {
|
||||
|
||||
}
|
||||
|
||||
void Update() {
|
||||
|
||||
if (health <= 0) {
|
||||
if (OnPlayerDeath != null) {
|
||||
OnPlayerDeath(); // invoke Action (if no subscribers event will be NULL, can cause errors)
|
||||
}
|
||||
|
||||
Destroy(GameObject); // needs to be notified
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```cs
|
||||
// example of script needing a reference to another
|
||||
public class GameUI : MonoBehaviour {
|
||||
|
||||
Player player; //instance of referenced GameObject to be found by its type
|
||||
|
||||
void Start(){
|
||||
GameObject playerObj = GameObject.Find("Player"); //reference to game object
|
||||
GameObject playerObj = GameObject.FindGameObjectWithTag("Tag"); //reference to game object
|
||||
player = playerObj.GetComponent<Player>(); // get script attached to the GameObject
|
||||
|
||||
player = FindObjectOfType<Player>(); // get reference to an object
|
||||
|
||||
// on event invocation all subscriber methods will be called
|
||||
player.OnPlayerDeath += GameOver; // subscribe method to event
|
||||
}
|
||||
|
||||
void Update() {
|
||||
DrawHealthBar(plyer.health); // call method passing data of player GameObject
|
||||
}
|
||||
|
||||
void DrawHealthBar(float playerHealth) {
|
||||
// implementation
|
||||
}
|
||||
|
||||
public void GameOver() {
|
||||
//game over screen
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Screen
|
||||
|
||||
### 2D Screen Measures
|
||||
|
||||
Aspect Ratio = `(screen_width [px]) / (screen_height [px])`
|
||||
Orthographic Size `[world units]` = `(screen_height [world units] / 2)`
|
||||
Aspect Ratio * Orthographic Size = `(screen_width [world units] / 2)`
|
||||
Screen Width `[world units]` = `(AspectRatio * OrthographicSize * 2)`
|
||||
|
||||
```cs
|
||||
screenWidth = Camera.main.aspect * Camera.main.orthographicSize * 2;
|
||||
```
|
||||
|
||||
## Scriptable Objects
|
||||
|
||||
Class to store data in stand alone assets, used to keep data out of scripts.
|
||||
Can be used as a template.
|
||||
|
||||
```c#
|
||||
[CreateAssetMenu(menuName = "ScriptableObjectName")] //enable creation of scriptable object
|
||||
public class ScriptableObjectName : ScriptableObject {
|
||||
//data structure here
|
||||
}
|
||||
```
|
||||
|
||||
### Game Object Serialization
|
||||
|
||||
```c#
|
||||
[SerializeField] type variable; //access game object from code
|
||||
```
|
||||
|
||||
### Game Object Data Access
|
||||
|
||||
```c#
|
||||
public type GetVariable(){
|
||||
return variable;
|
||||
}
|
||||
```
|
87
dotnet/unity/vectors-tranfrorms-space.md
Normal file
87
dotnet/unity/vectors-tranfrorms-space.md
Normal file
|
@ -0,0 +1,87 @@
|
|||
# Vector, Transform, Space
|
||||
|
||||
## Vector2, Vector3, Vector4
|
||||
|
||||
[Vector3 Docs](https://docs.unity3d.com/ScriptReference/Vector3.html)
|
||||
|
||||
Used to store positions, velocities and directions.
|
||||
|
||||
Magnitude = `sqrt(Math.pow(x, 2) + Math.pow(y, 2))`
|
||||
Direction = `(x / Magnitude, y / Magnitude)`
|
||||
|
||||
The direction is calculated by normalizing the vector to make it become a unit vector (*versor*).
|
||||
|
||||
```cs
|
||||
Vector3.x // x coord of vector
|
||||
Vector3.y // x coord of vector
|
||||
Vector3.z // x coord of vector
|
||||
|
||||
Vector3.magnitude
|
||||
Vector3.normalized
|
||||
|
||||
Vector3.up // Vector3(0, 1, 0)
|
||||
Vector3.down // Vector3(0, -1, 0)
|
||||
Vector3.left // Vector3(-1, 0, 0)
|
||||
Vector3.right // Vector3(1, 0, 0)
|
||||
Vector3.forward // Vector3(0, 0, 1)
|
||||
Vector3.back // Vector3(0, 0, -1)
|
||||
Vector3.one // Vector3(1, 1, 1)
|
||||
Vector3.zero // Vector3(0, 0, 0)
|
||||
Vector3.one // Vector3(1, 1, 1)
|
||||
```
|
||||
|
||||
### Operations
|
||||
|
||||
```cs
|
||||
Vector3(x, y, z) * n = Vector3(xn, yn, yz);
|
||||
Vector3(x, y, z) / n = Vector3(x / n, y / n, y / z);
|
||||
|
||||
Vector3(x1, y1, z1) + Vector3(x2, y2, z2) = Vector3(x1 + x2, y1 + y2, z1 + z2);
|
||||
Vector3(x1, y1, z1) - Vector3(x2, y2, z2) = Vector3(x1 - x2, y1 - y2, z1 - z2);
|
||||
|
||||
Quaternion.Euler(Vector3) // convert a Vector3 to a Quaternion
|
||||
```
|
||||
|
||||
### Movement
|
||||
|
||||
Speed = value m/s
|
||||
Velocity = Direction * Speed
|
||||
|
||||
MovementInFrame = Speed * timeSinceLastFrame
|
||||
|
||||
## Transform
|
||||
|
||||
[Transform Docs](https://docs.unity3d.com/ScriptReference/Transform.html)
|
||||
|
||||
```cs
|
||||
// properties
|
||||
transform.position // Vector3 - global position
|
||||
transform.localPosition // Vector3 - local position
|
||||
transform.rotation // Quaternion - global rotation
|
||||
transform.parent // Transform - parent of the object
|
||||
|
||||
transform.localScale = Vector3; // set object dimensions
|
||||
|
||||
// methods
|
||||
transform.Rotate(Vector3 * Time.deltaTime * speed, Space); // set rotation using vectors in selected space (Space.Self or Space.World)
|
||||
transform.Translate(Vector3 * Time.deltaTime * speed, Space); // set movement in selected space
|
||||
```
|
||||
|
||||
### Local, GLobal & Object Space
|
||||
|
||||
**Local Space**: Applies transformation relative to the *local* coordinate system (`Space.Self`).
|
||||
**Global Space**: Applies transformation relative to the *world* coordinate system (`Space.World`)
|
||||
|
||||
### Parenting
|
||||
|
||||
Changing the parent will make position, scale and rotation of the child object relative to the parent but keep the world space's position, rotation and scale the same.
|
||||
|
||||
Setting the parentele by script:
|
||||
|
||||
```cs
|
||||
public class ParentScript : MonoBehaviour {
|
||||
public Transform childTransform; // reference to the child object transform
|
||||
|
||||
childTransform.parent = transform; // when evaluated at runtime sets current object as parent of another
|
||||
}
|
||||
```
|
35
dotnet/xamarin/app.xaml.cs.md
Normal file
35
dotnet/xamarin/app.xaml.cs.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# App.xaml.cs
|
||||
|
||||
This `App` class is defined as `public` and derives from the Xamarin.Forms `Application` class.
|
||||
The constructor has just one responsibility: to set the `MainPage` property of the `Application` class to an object of type `Page`.
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace AppName
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
MainPage = new MainPage();
|
||||
}
|
||||
|
||||
protected override void OnStart()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnSleep()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnResume()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
51
dotnet/xamarin/app.xaml.md
Normal file
51
dotnet/xamarin/app.xaml.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
# App.xaml
|
||||
|
||||
## Root Tag
|
||||
|
||||
The `<Application>` tag begins with two XML namespace declarations, both of which are URIs.
|
||||
|
||||
The default namespace belongs to **Xamarin**. This is the XML namespace (`xmlns`) for elements in the file with no prefix, such as the `ContentPage` tag.
|
||||
The URI includes the year that this namespace came into being and the word *forms* as an abbreviation for Xamarin.Forms.
|
||||
|
||||
The second namespace is associated with a prefix of `x` by convention, and it belongs to **Microsoft**. This namespace refers to elements and attributes that are intrinsic to XAML and are found in every XAML implementation.
|
||||
The word *winfx* refers to a name once used for the .NET Framework 3.0, which introduced WPF and XAML.
|
||||
|
||||
The `x:Class` attribute can appear only on the root element of a XAML file. It specifies the .NET namespace and name of a derived class. The base class of this derived class is the root element.
|
||||
|
||||
In other words, this `x:Class` specification indicates that the `App` class in the `AppName` namespace derives from `Application`.
|
||||
That's exactly the same information as the `App` class definition in the `App.xaml.cs` file.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Application xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="AppName.App">
|
||||
</Application>
|
||||
```
|
||||
|
||||
## Shared Resources
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Application xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="AppName.App">
|
||||
|
||||
<!-- collection of shared resources definitions -->
|
||||
<Application.Resources>
|
||||
|
||||
<!-- Application resource dictionary -->
|
||||
<ResourceDictionary>
|
||||
<!-- Key-Value Pair -->
|
||||
<Type x:Key="DictKey">value<Type>
|
||||
</ResourceDictionary>
|
||||
|
||||
<!-- define a reusable style -->
|
||||
<Style x:Key="Style Name" TargetType="Element Type">
|
||||
<!-- set properties of the style -->
|
||||
<Setter Property="PropertyName" Value="PropertyValue">
|
||||
</Style>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
```
|
22
dotnet/xamarin/page.xaml.cs.md
Normal file
22
dotnet/xamarin/page.xaml.cs.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Page.xaml.cs
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace AppName
|
||||
{
|
||||
public partial class PageName : ContentPage
|
||||
{
|
||||
public PageName()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
86
dotnet/xamarin/page.xaml.md
Normal file
86
dotnet/xamarin/page.xaml.md
Normal file
|
@ -0,0 +1,86 @@
|
|||
# Page.xaml
|
||||
|
||||
## Anatomy of an app
|
||||
|
||||
The modern user interface is constructed from visual objects of various sorts. Depending on the operating system, these visual objects might go by different names—controls, elements, views, widgets—but they are all devoted to the jobs of presentation or interaction or both.
|
||||
|
||||
In Xamarin.Forms, the objects that appear on the screen are collectively called *visual elements* (`VisualElement` class).
|
||||
|
||||
They come in three main categories:
|
||||
|
||||
- page (`Page` class)
|
||||
- layout (`Layout` class)
|
||||
- view (`View` class)
|
||||
|
||||
A Xamarin.Forms application consists of one or more pages. A page usually occupies all (or at least a large area) of the screen.
|
||||
Some applications consist of only a single page, while others allow navigating between multiple pages.
|
||||
|
||||
On each page, the visual elements are organized in a parent-child hierarchy.
|
||||
Some layouts have a single child, but many layouts have multiple children that the layout arranges within itself. These children can be other layouts or views.
|
||||
|
||||
The term *view* in Xamarin.Forms denotes familiar types of presentation and interactive objects.
|
||||
|
||||
## Pages
|
||||
|
||||

|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
|
||||
<!-- Sets the Content property to a single View -->
|
||||
<ContentPage></ContentPage>
|
||||
|
||||
<!-- Manages two panes of information (Master and Detail property) -->
|
||||
<MasterDetailPage></MasterDetailPage>
|
||||
|
||||
<!-- Manages navigation among other pages using a stack-based architecture -->
|
||||
<NavigationPage></NavigationPage>
|
||||
|
||||
<!-- Allows navigation among child pages using tabs -->
|
||||
<TabbedPage></TabbedPage>
|
||||
|
||||
<!-- Displays full-screen content with a control template -->
|
||||
<TemplatePage></TemplatePage>
|
||||
|
||||
<!-- Allows navigation among child pages through finger swiping -->
|
||||
<CarouselPage></CarouselPage>
|
||||
```
|
||||
|
||||
## Layouts
|
||||
|
||||

|
||||
|
||||
- StackLayout: Organizes views linearly, either horizontally or vertically.
|
||||
- AbsoluteLayout: Organizes views by setting coordinates & size in terms of absolute values or ratios.
|
||||
- RelativeLayout: Organizes views by setting constraints relative to their parent's dimensions & position.
|
||||
- Grid: Organizes views in a grid of Rows and Columns
|
||||
- FlexLayout: Organizes views horizontally or vertically with wrapping.
|
||||
- ScrollView: Layout that's capable of scrolling its content.
|
||||
|
||||
### Grid Layout
|
||||
|
||||
```xml
|
||||
<!-- "<num>*" makes the dimensions proportional -->
|
||||
<Gird.RowDefinitions>
|
||||
<!-- insert a row in the layout -->
|
||||
<RowDefinition Height="2*"/>
|
||||
</Gird.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<!-- insert column in layout -->
|
||||
<ColumnDefinition Width=".5*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
```
|
||||
|
||||
## [Views](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-controls "XAML Views")
|
||||
|
||||
```xml
|
||||
<Image Source="" BackgroundColor="" [LayoutPosition]/>
|
||||
<!-- source contains reference to image file in Xamarin.[OS]/Resources/drawable -->
|
||||
|
||||
<!-- box to insert text -->
|
||||
<Editor Placeholder="placeholder text" [LayoutPosition]/>
|
||||
|
||||
<!-- clickable button -->
|
||||
<Button Text="button text" BackgroundColor="" Clicked="function_to_call" [LayoutPosition]/>
|
||||
```
|
35
dotnet/xamarin/view-model.cs.md
Normal file
35
dotnet/xamarin/view-model.cs.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# ViewModel.cs
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace AppName.ViewModels
|
||||
{
|
||||
class PageNameViewModel : INotifyPropertyChanged
|
||||
{
|
||||
public MainPageViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged; // event handler to notify changes
|
||||
|
||||
public string field; // needed, if substituted by ExprBody will cause infinite loop of access with the binding
|
||||
public string Property
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
|
||||
var args = new PropertyChangedEventArgs(nameof(Property)); // EVENT: let view know that the Property has changed
|
||||
PropertyChanged?.Invoke(this, args); // Invoke event to notify the view
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
41
dotnet/xamarin/xamarin-essentials.md
Normal file
41
dotnet/xamarin/xamarin-essentials.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Xamarin.Essentials
|
||||
|
||||
Android, iOS, and UWP offer unique operating system and platform APIs that developers have access to all in C# leveraging Xamarin.
|
||||
**Xamarin.Essentials** provides a *single cross-platform API* that works with any Xamarin.Forms, Android, iOS, or UWP application that can be accessed from shared code no matter how the user interface is created.
|
||||
|
||||
- [Accelerometer](https://docs.microsoft.com/en-us/xamarin/essentials/accelerometer): Retrieve acceleration data of the device in three dimensional space.
|
||||
- [App Information](https://docs.microsoft.com/en-us/xamarin/essentials/app-information): Find out information about the application.
|
||||
- [App Theme](https://docs.microsoft.com/en-us/xamarin/essentials/app-theme): Detect the current theme requested for the application.
|
||||
- [Barometer](https://docs.microsoft.com/en-us/xamarin/essentials/barometer): Monitor the barometer for pressure changes.
|
||||
- [Battery](https://docs.microsoft.com/en-us/xamarin/essentials/battery): Easily detect battery level, source, and state.
|
||||
- [Clipboard](https://docs.microsoft.com/en-us/xamarin/essentials/clipboard): Quickly and easily set or read text on the clipboard.
|
||||
- [Color Converters](https://docs.microsoft.com/en-us/xamarin/essentials/color-converters): Helper methods for System.Drawing.Color.
|
||||
- [Compass](https://docs.microsoft.com/en-us/xamarin/essentials/compass): Monitor compass for changes.
|
||||
- [Connectivity](https://docs.microsoft.com/en-us/xamarin/essentials/connectivity): Check connectivity state and detect changes.
|
||||
- [Detect Shake](https://docs.microsoft.com/en-us/xamarin/essentials/detect-shake): Detect a shake movement of the device.
|
||||
- [Device Display Information](https://docs.microsoft.com/en-us/xamarin/essentials/device-display): Get the device's screen metrics and orientation.
|
||||
- [Device Information](https://docs.microsoft.com/en-us/xamarin/essentials/device-information): Find out about the device with ease.
|
||||
- [Email](https://docs.microsoft.com/en-us/xamarin/essentials/email): Easily send email messages.
|
||||
- [File System Helpers](https://docs.microsoft.com/en-us/xamarin/essentials/file-system-helpers): Easily save files to app data.
|
||||
- [Flashlight](https://docs.microsoft.com/en-us/xamarin/essentials/flashlight): A simple way to turn the flashlight on/off.
|
||||
- [Geocoding](https://docs.microsoft.com/en-us/xamarin/essentials/geocoding): Geocode and reverse geocode addresses and coordinates.
|
||||
- [Geolocation](https://docs.microsoft.com/en-us/xamarin/essentials/geolocation): Retrieve the device's GPS location.
|
||||
- [Gyroscope](https://docs.microsoft.com/en-us/xamarin/essentials/gyroscope): Track rotation around the device's three primary axes.
|
||||
- [Launcher](https://docs.microsoft.com/en-us/xamarin/essentials/launcher): Enables an application to open a URI by the system.
|
||||
- [Magnetometer](https://docs.microsoft.com/en-us/xamarin/essentials/magnetometer): Detect device's orientation relative to Earth's magnetic field.
|
||||
- [MainThread](https://docs.microsoft.com/en-us/xamarin/essentials/main-thread): Run code on the application's main thread.
|
||||
- [Maps](https://docs.microsoft.com/en-us/xamarin/essentials/maps): Open the maps application to a specific location.
|
||||
- [Open Browser](https://docs.microsoft.com/en-us/xamarin/essentials/open-browser): Quickly and easily open a browser to a specific website.
|
||||
- [Orientation Sensor](https://docs.microsoft.com/en-us/xamarin/essentials/orientation-sensor): Retrieve the orientation of the device in three dimensional space.
|
||||
- [Permissions](https://docs.microsoft.com/en-us/xamarin/essentials/permissions): Check and request permissions from users.
|
||||
- [Phone Dialer](https://docs.microsoft.com/en-us/xamarin/essentials/phone-dialer): Open the phone dialer.
|
||||
- [Platform Extensions](https://docs.microsoft.com/en-us/xamarin/essentials/platform-extensions): Helper methods for converting Rect, Size, and Point.
|
||||
- [Preferences](https://docs.microsoft.com/en-us/xamarin/essentials/preferences): Quickly and easily add persistent preferences.
|
||||
- [Secure Storage](https://docs.microsoft.com/en-us/xamarin/essentials/secure-storage): Securely store data.
|
||||
- [Share](https://docs.microsoft.com/en-us/xamarin/essentials/share): Send text and website uris to other apps.
|
||||
- [SMS](https://docs.microsoft.com/en-us/xamarin/essentials/sms): Create an SMS message for sending.
|
||||
- [Text-to-Speech](https://docs.microsoft.com/en-us/xamarin/essentials/text-to-speech): Vocalize text on the device.
|
||||
- [Unit Converters](https://docs.microsoft.com/en-us/xamarin/essentials/unit-converters): Helper methods to convert units.
|
||||
- [Version Tracking](https://docs.microsoft.com/en-us/xamarin/essentials/version-tracking): Track the applications version and build numbers.
|
||||
- [Vibrate](https://docs.microsoft.com/en-us/xamarin/essentials/vibrate): Make the device vibrate.
|
||||
- [Web Authenticator](https://docs.microsoft.com/en-us/xamarin/essentials/web-authenticator): Start web authentication flows and listen for a callback.
|
Loading…
Add table
Add a link
Reference in a new issue