.Net core学习笔记

Posted by Kerwen Blog on November 2, 2021

基础知识

Startup 类

Startup 类包括:

ConfigureServices方法注册应用所需的服务,在 Configure 方法配置应用服务之前,由主机调用。
Configure方法创建应用的请求处理管道,中间件

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<RazorPagesMovieContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("RazorPagesMovieContext")));

        services.AddControllersWithViews();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapDefaultControllerRoute();
            endpoints.MapRazorPages();
        });
    }
}

中间件

请求处理管道由一系列中间件组件组成。 每个组件在 HttpContext 上执行操作,调用管道中的下一个中间件或终止请求。
按照惯例,通过在 Startup.Configure 方法中调用 Use… 扩展方法,向管道添加中间件组件。

Host(主机)

ASP.NET Core 应用在启动时构建主机。 主机封装应用的所有资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Logging

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class TodoController : ControllerBase
{
    private readonly ILogger _logger;

    public TodoController(ILogger<TodoController> logger)
    {
        _logger = logger;
    }

    [HttpGet("{id}", Name = "GetTodo")]
    public ActionResult<TodoItem> GetById(string id)
    {
        _logger.LogInformation(LoggingEvents.GetItem, "Getting item {Id}", id);
        
        // Item lookup code removed.
        
        if (item == null)
        {
            _logger.LogWarning(LoggingEvents.GetItemNotFound, "GetById({Id}) NOT FOUND", id);
            return NotFound();
        }
        
        return item;
    }
}

依赖注入

ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式
示例:
IMyDependency 接口定义 WriteMessage 方法

1
2
3
4
public interface IMyDependency
{
    void WriteMessage(string message);
}

具体类 MyDependency 实现此接口

1
2
3
4
5
6
7
public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

Startup中注册服务

1
2
3
4
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency>();
}

在实际应用中,请求 IMyDependency 服务并用于调用 WriteMessage 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class IndexModel : PageModel
{
    private readonly IMyDependency _myDependency;

    public IndexModel(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

不使用具体类型 MyDependency,仅使用它实现的 IMyDependency 接口。 这样可以轻松地更改控制器使用的实现,而无需修改控制器。

可以将相关的注册组移动到扩展方法以注册服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
            this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }
    }
}

下面的 ConfigureServices 方法使用新扩展方法来注册服务

1
2
3
4
5
6
7
public void ConfigureServices(IServiceCollection services)
{
    services.AddConfig(Configuration)
            .AddMyDependencyGroup();

    services.AddRazorPages();
}

避免使用服务定位器模式。 例如,可以使用 DI 代替时,不要调用 GetService 来获取服务实例:

错误示范:

1
2
3
4
5
6
7
8
9
10
public class MyClass
{
    public void MyMethod()
    {
        var optionMonitor = _services.GetService<IOptionsMonitor<MyOptions>>();
        var option = optionMonitor.CurrentValue.Option;

        ...
    }
}

正确示范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyClass
{
    private readonly IOptionsMonitor<MyOptions> _optionsMonitor;

    public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
    {
        _optionsMonitor = optionsMonitor;
    }

    public void MyMethod()
    {
        var option = _optionsMonitor.CurrentValue.Option;

        ...
    }
}

.net

安装https证书

1
2
3
4
dotnet dev-certs https --trust


Dotnet watch run

Add controller

DataContext

1
2
3
4
    ervices.AddDbContext<DataContext>(options =>
    {
        options.UseSqlite(_Config.GetConnectionString("DefaultConnection"));
    });

Dotnet-ef

1
dotnet ef migrations add InitialCreate -o Data/Migrations

create scheme

Angular

extension:
Angular language servcie Angular snippets Bracket Pair Colorizer 2

ng add ngx-bootstrap
npm install font-awesome

app.UseCors(policy => policy.AllowAnyHeader().AllowAnyMethod().WithOrigins(“https://localhost:4200”));

https:

1
2
3
    "sslKey": "./ssl/server.key",
    "sslCert": "./ssl/server.cert",
    "ssl": true,

Register/Login

Save hashed password and salt into database

1
2
3
4
5
6
7
8
9
10
    using var hamc = new HMACSHA512();
    var user = new AppUser
    {
        UserName = username,
        PasswordHash = hamc.ComputeHash(Encoding.UTF8.GetBytes(password)),
        PasswordSalt = hamc.Key
    };
    this.context.Users.Add(user);
    await this.context.SaveChangesAsync();
    return user;

DTOs(Data Transfer Objects)

parameter validation: [Required]

dotnet ef database drop
dotnet ef database update

JSON Web Tokens(JWT) header DATA nbf, exp, iat Signature

img

Add token service:
services.AddScoped<ITokenService, TokenService>();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public string CreateToken(AppUser user)
    {
        var claims = new List<Claim>
        {
            new Claim(JwtRegisteredClaimNames.NameId, user.UserName)
        };

        var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(claims),
            Expires = DateTime.Now.AddDays(7),
            SigningCredentials = creds
        };
        var tokenHandler = new JwtSecurityTokenHandler();
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }

“TokenKey”:”super secret unguessable key”,

Add middleware

1
2
3
4
5
6
7
8
9
10
11
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_Config["TokenKey"])),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                };
            });

Angular login

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    <form #loginForm="ngForm" class="form-inline mt-2 mt-md-0" (ngSubmit)="login()" autocomplete="off">
          <input name="username" [(ngModel)]="model.username" class="form-control mr-sm-2" type="text" placeholder="Username">
          <input name="password" [(ngModel)]="model.password" class="form-control mr-sm-2" type="password" placeholder="Password">
          <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Login</button>
    </form>



    getMembers() {
        return this.http.get('api/users').pipe(
            map(members => {
                cosnole.log(member.id);
                return member.id;
            })
        )
    }

Observables的pipe and map

1
2
    private currentUserSource = new ReplaySubject<User>(1);
    currentUser$ = this.currentUserSource.asObservable();

pass parameter to child

Child pass parameter to parent

child:

1
2
    @Output() cancelRegister = new EventEmitter();
    this.cancelRegister.emit(false);

Parent:

1
2
3
4
    <app-register (cancelRegister)="cancelRegisterMode($event)"></app-register>
      cancelRegisterMode(event:boolean):void{
            this.registerMode = event;
        }

npm install ngx-toastr

Angular route guard:

1
2
3
4
5
6
7
8
9
10
11
12
13
ng g guard auth

canActivate(): Observable<boolean> {
    return this.accountService.currentUser$.pipe(
        map(user => {
            if(user) return true;
            this.toastr.error('You shall not pass!');
            return false;
        })
    )
}

{path:'members', component:MemberListComponent, canActivate:[AuthGuard]},

Add a dummy route

1
2
3
4
5
6
7
8
9
10
11
{
    path:'',
    runGuardsAndResolvers:'always',
    canActivate:[AuthGuard],
    children:[
        {path:'members', component:MemberListComponent, canActivate:[AuthGuard]},
        {path:'members/:id', component:MemberDetailComponent},
        {path:'lists', component:ListsComponent},
        {path:'messages', component:MessagesComponent},
    ]
},

ng-container is normally a better technique for when you’re using conditionals. because of the fact it doesn’t generate any HTML and it won’t interfere with any of your styling when you use it.

1
2
3
4
5
6
7
8
9
10
11
<ng-container *ngIf="accountService.currentUser$ | async">      
    <li class="nav-item">
      <a class="nav-link" routerLink='/members' routerLinkActive='active'>Matches</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" routerLink='/lists' routerLinkActive='active'>Lists</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" routerLink='/messages' routerLinkActive='active'>Messages</a>
    </li>
</ng-container>

npm install bootswatch

shared module:

1
ng g m shared --flat

Error handling

1
2
3
    [Required]
    [StringLength(8, MinimumLength = 4)]
    public string Password { get; set; }   

Exception handing middleware

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    app.UseMiddleware<ExceptionMiddleware>();

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await this.next(context);
        }
        catch(Exception ex)
        {
            this.logger.LogError(ex, ex.Message);
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;

            var response = this.Env.IsDevelopment()
            ? new ApiException(context.Response.StatusCode, ex.Message, ex.StackTrace?.ToString())
            : new ApiException(context.Response.StatusCode, "Internal Server Error", "");

            var options = new JsonSerializerOptions();

            var json = JsonSerializer.Serialize(response, options);
            await context.Response.WriteAsync(json);
        }
    }

Error handing in Angular

1
2
3
4
5
6
7
8
9
10
    ng g interceptor --skip-tests

    providers: [
        {provide: HTTP_INTERCEPTORS, useClass:ErrorInterceptor, multi:true},
    ],


    "lib": [
        "es2019",
    ]

扩展API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public static class DateTimeExtensions
    {
        public static int CalculateAge(this DateTime dob){
            var today = DateTime.Today;
            var age = today.Year - dob.Year;
            if(dob.Date > today.AddYears(-age)) age--;
            return age;
        }
    }


    public DateTime DateOfBirth { get; set; }
    public int GetAge()
    {
        return DateOfBirth.CalculateAge();
    }

为什么可以直接调用?

1
2
    dotnet ef migrations add ExtendedUserEntity
    dotnet ef database update

解析Json 数据

1
2
using System.Text.Json;
var users = JsonSerializer.Deserialize<List<AppUser>>(userData);

AutoMapperProfiles.cs

1
2
3
4
5
6
7
8
9
10
11
    public AutoMapperProfiles()
    {
        CreateMap<AppUser, MemberDto>()
        .ForMember(dest => dest.PhotoUrl, opt => opt.MapFrom(src => 
        src.Photos.FirstOrDefault(x => x.IsMain).Url))
        .ForMember(dest => dest.Age, opt => opt.MapFrom(src => src.DateOfBirth.CalculateAge()));
        CreateMap<Photo, PhotoDto>();
    }


    services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);

问题

  1. dotnet ef migrations add ExtendedUserEntity dotnet ef database update

  2. 什么是DTO

Reference

数字签名是什么
什么是数字签名和证书
HTTP2和HTTPS来不来了解一下
Entity Framework Core