mirror of
https://github.com/m-lamonaca/dev-notes.git
synced 2025-04-06 10:56:41 +00:00
Notes on .NET 6 features (#6)
- blazor components - `field` keyword - with-expression for anonymous types - namespace semplification - lambda signature inference - static methods and properties on interfaces - .NET 6 LINQ improvements - global usings notes - null parameter checking - required properties - native memory allocation - Minimal APIs - net 6 implicit imports
This commit is contained in:
parent
56a963bec8
commit
b2abff42c4
5 changed files with 787 additions and 323 deletions
|
@ -335,3 +335,36 @@ public class StateContainer
|
|||
|
||||
[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");
|
||||
```
|
||||
|
|
163
DotNet/ASP.NET/Minimal API.md
Normal file
163
DotNet/ASP.NET/Minimal API.md
Normal file
|
@ -0,0 +1,163 @@
|
|||
# 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) { /* ... */ }
|
||||
```
|
||||
|
||||
## 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");
|
||||
});
|
||||
```
|
||||
|
||||
## 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();
|
||||
```
|
|
@ -1,5 +1,181 @@
|
|||
# ASP .NET REST API
|
||||
|
||||
## Startup class
|
||||
|
||||
- Called by `Program.cs`
|
||||
|
||||
```cs
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace <Namespace>
|
||||
{
|
||||
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 container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers(); // controllers w/o views
|
||||
//or
|
||||
services.AddControllersWithViews(); // MVC Controllers
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## DB Context (EF to access DB)
|
||||
|
||||
NuGet Packages to install:
|
||||
|
||||
- `Microsoft.EntityFrameworkCore`
|
||||
- `Microsoft.EntityFrameworkCore.Tools`
|
||||
- `Microsoft.EntityFrameworkCore.Design` *or* `Microsoft.EntityFrameworkCore.<db_provider>.Design`
|
||||
- `Microsoft.EntityFrameworkCore.<db_provider>`
|
||||
|
||||
In `AppDbContext.cs`:
|
||||
|
||||
```cs
|
||||
using <Namespace>.Model;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace <Namespace>.Repo
|
||||
{
|
||||
public class AppDbContext : DbContext
|
||||
{
|
||||
public AppDbContext(DbContextOptions<ProjectContext> 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; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In `appsettings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"CommanderConnection" : "Server=<server>;Database=<database>;UID=<user>;Pwd=<password>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In `Startup.cs`:
|
||||
|
||||
```cs
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// SqlServer is the db used in this example
|
||||
services.AddDbContext<CommanderContext>(option => option.UseSqlServer(Configuration.GetConnectionString("CommanderConnection")));
|
||||
services.AddControllers();
|
||||
}
|
||||
```
|
||||
|
||||
### Migrations
|
||||
|
||||
- Mirroring of model in the DB.
|
||||
- Will create & update DB Schema if necessary
|
||||
|
||||
In Package Manager Shell:
|
||||
|
||||
```ps1
|
||||
add-migrations <migration_name>
|
||||
update-database # use the migrations to modify the db
|
||||
```
|
||||
|
||||
## Repository
|
||||
|
||||
In `IEntityRepo`:
|
||||
|
||||
```cs
|
||||
using <Namespace>.Model;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace <Namespace>.Repository
|
||||
{
|
||||
public interface IEntityRepo
|
||||
{
|
||||
IEnumerable<Entity> SelectAll();
|
||||
Entity SelectOneById(int id);
|
||||
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In `EntityRepo`:
|
||||
|
||||
```cs
|
||||
using <Namespace>.Model;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace <Namespace>.Repo
|
||||
{
|
||||
public class EntityRepo : IEntityRepo
|
||||
{
|
||||
private readonly AppDbContext _context;
|
||||
|
||||
public EntityRepo(AppDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public IEnumerable<Entity> SelectAll()
|
||||
{
|
||||
return _context.Entities.ToList(); // linq query (ToList()) becomes sql query
|
||||
}
|
||||
|
||||
public Entity SelectOneById(int id)
|
||||
{
|
||||
return _context.Entities.FirstOrDefault(p => p.Id == id);
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Data Transfer Objects (DTOs)
|
||||
|
||||
A **DTO** is an object that defines how the data will be sent and received over the network (usually as JSON).
|
||||
|
@ -7,28 +183,38 @@ Without a DTO the JSON response (or request) could contain irrelevant, wrong or
|
|||
Moreover, by decoupling the JSON response from the actual data model, it's possible to change the latter without breaking the API.
|
||||
DTOs must be mapped to the internal methods.
|
||||
|
||||
In `EntityDTO.cs`:
|
||||
Required NuGet Packages:
|
||||
|
||||
- AutoMapper.Extensions.Microsoft.DependencyInjection
|
||||
|
||||
In `StartUp.cs`:
|
||||
|
||||
```cs
|
||||
using AutoMapper;
|
||||
|
||||
// ...
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// other services
|
||||
|
||||
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); // set automapper service
|
||||
}
|
||||
```
|
||||
|
||||
In `Entity<CrudOperation>DTO.cs`:
|
||||
|
||||
```cs
|
||||
namespace <Namespace>.DTOs
|
||||
{
|
||||
// define the data to be serialized in JSON (differs from model)
|
||||
public class EntityDTO
|
||||
// define the data to be serialized in JSON (can differ from model)
|
||||
public class EntityCrudOpDTO // e.g: EntityReadDTO, ...
|
||||
{
|
||||
// only properties to be serialized
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DTO mapping with Automapper
|
||||
|
||||
Required NuGet Packages:
|
||||
|
||||
- `AutoMapper`
|
||||
- `AutoMapper.Extensions.Microsoft.DependencyInjection`
|
||||
|
||||
A good way to organize mapping configurations is with *profiles*.
|
||||
|
||||
In `EntitiesProfile.cs`:
|
||||
|
||||
```cs
|
||||
|
@ -42,7 +228,7 @@ namespace <Namespace>.Profiles
|
|||
{
|
||||
public EntitiesProfile()
|
||||
{
|
||||
CreateMap<Entity, EntityDTO>(); // map entity to it's DTO
|
||||
CreateMap<Entity, EntityCrudOpDTO>(); // map entity to it's DTO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,35 +269,68 @@ namespace <App>.Controllers
|
|||
[ApiController]
|
||||
public class EntitiesController : ControllerBase // MVC controller w/o view
|
||||
{
|
||||
// service or repo (DAL) injection
|
||||
|
||||
private readonly ICommandRepo _repo;
|
||||
private readonly IMapper _mapper; // AutoMapper class
|
||||
|
||||
[HttpGet] // GET api/endpoint
|
||||
public ActionResult<EntityDTO> SelectAllEntities()
|
||||
public EntitiesController(IEntityRepo repository, IMapper mapper) // injection og the dependency
|
||||
{
|
||||
...
|
||||
_repo = repository;
|
||||
_mapper = mapper
|
||||
}
|
||||
|
||||
return Ok(_mapper.Map<EntityDTO>(entity));
|
||||
[HttpGet] // GET api/endpoint
|
||||
public ActionResult<IEnumerable<EntityCrudOpDTO>> SelectAllEntities()
|
||||
{
|
||||
var results = _repo.SelectAll();
|
||||
|
||||
return Ok(_mapper.Map<EntityCrudOpDTO>(results)); // return an action result OK (200) with the results
|
||||
}
|
||||
|
||||
// default binding source: [FromRoute]
|
||||
[HttpGet("{id}")] // GET api/endpoint/{id}
|
||||
public ActionResult<EntityCrudOpDTO> SelectOneEntityById(int id)
|
||||
{
|
||||
var result = _repo.SelectOneById(id);
|
||||
|
||||
if(result != null)
|
||||
{
|
||||
return Ok(_mapper.Map<EntityCrudOp>(result)); // transform entity to it's DTO
|
||||
}
|
||||
|
||||
return NotFound(); // ActionResult NOT FOUND (404)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Simple API Controller
|
||||
## Controller (With View)
|
||||
|
||||
```cs
|
||||
[Route("api/endpoint")]
|
||||
[ApiController]
|
||||
public class EntitiesController : ControllerBase
|
||||
using <App>.Model;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace <App>.Controllers
|
||||
{
|
||||
// service or repo (DAL) injection
|
||||
[Route("api/endpoint")]
|
||||
[ApiController]
|
||||
public class EntitiesController : Controller
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
public EntitiesController(AppDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public ActionResult<TEntity> SelectAll()
|
||||
public IActionResult SelectAll()
|
||||
{
|
||||
...
|
||||
return Ok(entity);
|
||||
return Json(new { data = _db.Entities.ToList() }); // json view
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
577
DotNet/C#/C#.md
577
DotNet/C#/C#.md
File diff suppressed because it is too large
Load diff
|
@ -47,16 +47,24 @@ The `Where` and `Select` methods are examples of LINQ operators. A LINQ operator
|
|||
```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<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(Func<T, bool> predicate) // first element to match predicate or default(T)
|
||||
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
|
||||
|
||||
|
@ -72,5 +80,5 @@ IEnumerable<TFirst>.Zip(IEnumerable<TSecond> enumerable); // Produces a sequence
|
|||
```cs
|
||||
Enumerable.Method(IEnumerable<T> source, args);
|
||||
// if extension method same as
|
||||
IEnumerable<T>.Method(args)
|
||||
IEnumerable<T>.Method(args);
|
||||
```
|
||||
|
|
Loading…
Add table
Reference in a new issue