خودکار کردن تعاریف DbSetها در EF Code first
افزودن خودکار کلاسهای تنظیمات نگاشتها در EF Code first
/// <summary> /// دستور العمل /// </summary> class Recipe { public int Id { get; set; } public string Name { get; set; } } /// <summary> /// بازخورد /// </summary> class Review { public int RecipeId { get; set; } public string ReviewText { get; set; } }
Recipe[] recipes = { new Recipe {Id = 1, Name = "Mashed Potato"}, new Recipe {Id = 2, Name = "Crispy Duck"}, new Recipe {Id = 3, Name = "Sachertorte"} }; Review[] reviews = { new Review {RecipeId = 1, ReviewText = "Tasty!"}, new Review {RecipeId = 1, ReviewText = "Not nice :("}, new Review {RecipeId = 1, ReviewText = "Pretty good"}, new Review {RecipeId = 2, ReviewText = "Too hard"}, new Review {RecipeId = 2, ReviewText = "Loved it"} }; var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId select new //anonymous type { RecipeName = recipe.Name, RecipeReview = review.ReviewText }; foreach (var item in query) { Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}"); }
Mashed Potato-Tasty! Mashed Potato-Not nice :( Mashed Potato-Pretty good Crispy Duck-Too hard Crispy Duck-Loved it
var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId into reviewGroup select new //anonymous type { RecipeName = recipe.Name, Reviews = reviewGroup//collection of related reviews }; foreach (var item in query) { Console.WriteLine($"Review for {item.RecipeName}"); foreach (var review in item.Reviews) { Console.WriteLine($"-{review.ReviewText}"); } }
Review for Mashed Potato -Tasty! -Not nice :( -Pretty good Review for Crispy Duck -Too hard -Loved it Review for Sachertorte
var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId into reviewGroup from rg in reviewGroup.DefaultIfEmpty() select new //anonymous type { RecipeName = recipe.Name, //RecipeReview = rg.ReviewText SystemNullException RecipeReview = (rg == null ? "n/a" : rg.ReviewText) }; foreach (var item in query) { Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}"); }
Mashed Potato-Tasty! Mashed Potato-Not nice :( Mashed Potato-Pretty good Crispy Duck-Too hard Crispy Duck-Loved it Sachertorte-n/a
var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId into reviewGroup from rg in reviewGroup.DefaultIfEmpty(new Review { ReviewText = "N/A" }) select new //anonymous type { RecipeName = recipe.Name, RecipeReview = rg.ReviewText }; foreach (var item in query) { Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}"); }
Mashed Potato-Tasty! Mashed Potato-Not nice :( Mashed Potato-Pretty good Crispy Duck-Too hard Crispy Duck-Loved it Sachertorte-N/A
/// <summary> /// Validate IR National Code /// </summary> /// <param name="nationalcode">National Code</param> /// <param name="lastNumber">Last Number Of National Code</param> /// <returns></returns> public static bool IsValidNationalCode(this string nationalcode, out int lastNumber) { lastNumber = -1; if (!nationalcode.IsItNumber()) return false; var invalid = new[] { "0000000000", "1111111111", "2222222222", "3333333333", "4444444444", "5555555555", "6666666666", "7777777777", "8888888888", "9999999999" }; if (invalid.Contains(nationalcode)) return false; var array = nationalcode.ToCharArray(); if (array.Length != 10) return false; var j = 10; var sum = 0; for (var i = 0; i < array.Length - 1; i++) { sum += Int32.Parse(array[i].ToString(CultureInfo.InvariantCulture)) * j; j--; } var diff = sum % 11; if (diff < 2) { lastNumber = diff; return diff == Int32.Parse(array[9].ToString(CultureInfo.InvariantCulture)); } var temp = Math.Abs(diff - 11); lastNumber = temp; return temp == Int32.Parse(array[9].ToString(CultureInfo.InvariantCulture)); }
عموما برای درج فایلهای ثابت اسکریپتها و شیوهنامههای سایت، از روش متداول زیر استفاده میشود:
<link rel="stylesheet" href="/css/site.css" /> <script src="/js/site.js"></script>
مشکلی که به همراه این روش وجود دارد، مطلع سازی کاربران و مرورگر، از تغییرات آنهاست؛ چون این فایلهای ثابت، توسط مرورگرها کش شده و با فشردن دکمههایی مانند Ctrl+F5 و بهروز شدن کش مرورگر، به نگارش جدید، ارتقاء پیدا میکنند. برای رفع این مشکل حداقل دو روش وجود دارد:
الف) هربار نام این فایلها را تغییر دهیم. برای مثال بجای نام قدیمی site.css، از نام جدید site.v.1.1.css استفاده کنیم.
ب) یک کوئری استرینگ متغیر را به نام ثابت این فایلها، اضافه کنیم.
که در این بین، روش دوم متداولتر و معقولتر است. برای این منظور، ASP.NET Core به همراه ویژگی توکاری است به نام asp-append-version که اگر آنرا به تگهای اسکریپت و link اضافه کنیم:
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> <script src="~/js/site.js" asp-append-version="true"></script>
این کوئری استرینگ را به صورت خودکار محاسبه کرده و به آدرس فایل درج شده اضافه میکند؛ با خروجیهایی شبیه به مثال زیر:
<link rel="stylesheet" href="/css/site.css?v=AAs5qCYR2ja7e8QIduN1jQ8eMcls-cPxNYUozN3TJE0" /> <script src="/js/site.js?v=NO2z9yI9csNxHrDHIeTBBfyARw3PX_xnFa0bz3RgnE4"></script>
ASP.NET Core در اینجا هش فایلهای یافت شده را با استفاده از الگوریتم SHA256 محاسبه و url encode کرده و به صورت یک کوئری استرینگ، به انتهای آدرس فایلها اضافه میکند. به این ترتیب با تغییر محتوای این فایلها، این هش نیز تغییر میکند و مرورگر بر این اساس، همواره آخرین نگارش ارائه شده را از سرور دریافت خواهد کرد. نتیجهی این محاسبات نیز به صورت خودکار کش میشود و همچنین با استفاده از یک File Watcher در پشت صحنه، تغییرات این فایلها هم بررسی میشوند. یعنی اگر فایلی تغییر کرد، نیازی به ریاستارت برنامه نیست و محاسبات جدید و کش شدن مجدد آنها، به صورت خودکار انجام میشود.
البته این ویژگی هنوز به Blazor اضافه نشدهاست؛ اما امکان استفادهی از زیر ساخت ویژگی asp-append-version با کدنویسی مهیا است که در ادامه با استفاده از آن، کامپوننتی را مخصوص Blazor SSR، تهیه میکنیم.
دسترسی به زیر ساخت محاسباتی ویژگی asp-append-version با کدنویسی
زیرساخت محاسباتی ویژگی asp-append-version، با استفاده از سرویس توکار IFileVersionProvider به صورت زیر قابل دسترسی است:
public static class FileVersionHashProvider { private static readonly string ProcessExecutableModuleVersionId = Assembly.GetEntryAssembly()!.ManifestModule.ModuleVersionId.ToString("N"); public static string GetFileVersionedPath(this HttpContext httpContext, string filePath, string? defaultHash = null) { ArgumentNullException.ThrowIfNull(httpContext); var fileVersionedPath = httpContext.RequestServices.GetRequiredService<IFileVersionProvider>() .AddFileVersionToPath(httpContext.Request.PathBase, filePath); return IsEmbeddedOrNotFound(fileVersionedPath, filePath) ? QueryHelpers.AddQueryString(filePath, new Dictionary<string, string?>(StringComparer.Ordinal) { { "v", defaultHash ?? ProcessExecutableModuleVersionId } }) : fileVersionedPath; } private static bool IsEmbeddedOrNotFound(string fileVersionedPath, string filePath) => string.Equals(fileVersionedPath, filePath, StringComparison.Ordinal); }
در برنامههای Blazor SSR، دسترسی کاملی به HttpContext وجود دارد و همانطور که مشاهده میکنید، این سرویس نیز به اطلاعات آن جهت محاسبهی هش فایل معرفی شدهی به آن، نیاز دارد. در اینجا اگر هش قابل محاسبه نبود، از هش فایل اسمبلی جاری استفاده خواهد شد.
ساخت کامپوننتهایی برای درج خودکار هش فایلهای اسکریپتها
یک نمونه روش استفادهی از متد الحاقی GetFileVersionedPath فوق را در کامپوننت DntFileVersionedJavaScriptSource.razor زیر میتوانید مشاهده کنید:
@if (!string.IsNullOrWhiteSpace(JsFilePath)) { <script src="@HttpContext.GetFileVersionedPath(JsFilePath)" type="text/javascript"></script> } @code{ [CascadingParameter] public HttpContext HttpContext { set; get; } = null!; [Parameter] [EditorRequired] public required string JsFilePath { set; get; } }
با استفاده از HttpContext مهیای در برنامههای Blazor SSR، متد الحاقی GetFileVersionedPath به همراه مسیر فایل js. مدنظر، در صفحه درج میشود.
برای مثال یک نمونه از استفادهی آن، به صورت زیر است:
<DntFileVersionedJavaScriptSource JsFilePath="/lib/quill/dist/quill.js"/>
در نهایت با اینکار، یک چنین خروجی در صفحه درج خواهد شد که با تغییر محتوای فایل quill.js، هش متناظر با آن به صورت خودکار بهروز خواهد شد:
<scriptsrc="/lib/quill/dist/quill.js?v=5q7uUOOlr88Io5YhQk3lgYcoB_P3-5Awq1lf0rRa7-Y" type="text/javascript"></script>
شبیه به همین کار را برای شیوهنامهها هم میتوان تکرار کرد و کدهای آن، تفاوت آنچنانی با کامپوننت فوق ندارند.
Task < bool > SavePostedFileAsync ( IFormFile formFile , string destinationDirectoryName , bool allowOverwrite );
namespace BlazorStateManagement.Stores { public interface IAction { public string Name { get; } } }
namespace BlazorStateManagement.Stores.CounterStore { public class IncrementAction : IAction { public const string Increment = nameof(Increment); public string Name { get; } = Increment; } public class DecrementAction : IAction { public const string Decrement = nameof(Decrement); public string Name { get; } = Decrement; } }
using System; namespace BlazorStateManagement.Stores { public interface IActionDispatcher { void Dispatch(IAction action); void Subscribe(Action<IAction> actionHandler); void Unsubscribe(Action<IAction> actionHandler); } public class ActionDispatcher : IActionDispatcher { private Action<IAction> _actionHandlers; public void Subscribe(Action<IAction> actionHandler) => _actionHandlers += actionHandler; public void Unsubscribe(Action<IAction> actionHandler) => _actionHandlers -= actionHandler; public void Dispatch(IAction action) => _actionHandlers?.Invoke(action); } }
namespace BlazorStateManagement.Client { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); // ... builder.Services.AddScoped<IActionDispatcher, ActionDispatcher>(); // ... } } }
using System; namespace BlazorStateManagement.Stores.CounterStore { public interface ICounterStore : IDisposable { CounterState State { get; } void AddStateChangeListener(Action listener); void BroadcastStateChange(); void RemoveStateChangeListener(Action listener); } }
using System; namespace BlazorStateManagement.Stores.CounterStore { public class CounterStore : ICounterStore { private readonly CounterState _state = new(); private bool _isDisposed; private Action _listeners; private readonly IActionDispatcher _actionDispatcher; public CounterStore(IActionDispatcher actionDispatcher) { _actionDispatcher = actionDispatcher ?? throw new ArgumentNullException(nameof(actionDispatcher)); _actionDispatcher.Subscribe(HandleActions); } private void HandleActions(IAction action) { switch (action) { case IncrementAction: IncrementCount(); break; case DecrementAction: DecrementCount(); break; } } public CounterState State => _state; private void IncrementCount() { _state.Count++; BroadcastStateChange(); } private void DecrementCount() { _state.Count--; BroadcastStateChange(); } public void AddStateChangeListener(Action listener) => _listeners += listener; public void RemoveStateChangeListener(Action listener) => _listeners -= listener; public void BroadcastStateChange() => _listeners.Invoke(); public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_isDisposed) { try { if (disposing) { _actionDispatcher.Unsubscribe(HandleActions); } } finally { _isDisposed = true; } } } } }
private void HandleActions(IAction action) { switch (action) { case IncrementAction: IncrementCount(); break; case DecrementAction: DecrementCount(); break; } }
@inject ICounterStore CounterStore @code { private void IncrementCount() { CounterStore.IncrementCount(); }
@using BlazorStateManagement.Stores
// ... @inject IActionDispatcher ActionDispatcher @code { private void IncrementCount() { ActionDispatcher.Dispatch(new IncrementAction()); }
using System.Security.Claims; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Server; namespace BlazorServerTestDynamicAccess.Services; public class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider { private readonly IServiceScopeFactory _scopeFactory; public CustomAuthenticationStateProvider(ILoggerFactory loggerFactory, IServiceScopeFactory scopeFactory) : base(loggerFactory) => _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory)); protected override TimeSpan RevalidationInterval { get; } = TimeSpan.FromMinutes(30); protected override async Task<bool> ValidateAuthenticationStateAsync( AuthenticationState authenticationState, CancellationToken cancellationToken) { // Get the user from a new scope to ensure it fetches fresh data var scope = _scopeFactory.CreateScope(); try { var userManager = scope.ServiceProvider.GetRequiredService<IUsersService>(); return await ValidateUserAsync(userManager, authenticationState?.User); } finally { if (scope is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync(); } else { scope.Dispose(); } } } private async Task<bool> ValidateUserAsync(IUsersService userManager, ClaimsPrincipal? principal) { if (principal is null) { return false; } var userIdString = principal.FindFirst(ClaimTypes.UserData)?.Value; if (!int.TryParse(userIdString, out var userId)) { return false; } var user = await userManager.FindUserAsync(userId); return user is not null; } }
<!-- Required for routing --> <script src="~/node_modules/angular2/bundles/router.dev.js"></script>
<!DOCTYPE html> <html> <head> <base href="/">
//same as before ... import { ROUTER_PROVIDERS } from 'angular2/router'; //same as before ... @Component({ //same as before … providers: [ ProductService, HTTP_PROVIDERS, ROUTER_PROVIDERS ] }) //same as before ...
<div class='panel panel-primary'> <div class='panel-heading'> {{pageTitle}} </div> </div>
import { Component } from 'angular2/core'; @Component({ templateUrl: 'app/products/product-detail.component.html' }) export class ProductDetailComponent { pageTitle: string = 'Product Detail'; }
{ path: '/products', name: 'Products', component: ProductListComponent },
//same as before … import { ROUTER_PROVIDERS, RouteConfig } from 'angular2/router'; //same as before … @Component({ //same as before … }) @RouteConfig([ { path: '/welcome', name: 'Welcome', component: WelcomeComponent, useAsDefault: true }, { path: '/products', name: 'Products', component: ProductListComponent } ]) export class AppComponent { pageTitle: string = "DNT AngularJS 2.0 APP"; }
import { Component } from 'angular2/core'; @Component({ templateUrl: 'app/home/welcome.component.html' }) export class WelcomeComponent { public pageTitle: string = "Welcome"; }
<div class="panel panel-primary"> <div class="panel-heading"> {{pageTitle}} </div> <div class="panel-body"> <h3 class="text-center">Default page</h3> </div> </div>
import { WelcomeComponent } from './home/welcome.component';
@Component({ //same as before … template: ` <div> <nav class='navbar navbar-default'> <div class='container-fluid'> <a class='navbar-brand'>{{pageTitle}}</a> <ul class='nav navbar-nav'> <li><a [routerLink]="['Welcome']">Home</a></li> <li><a [routerLink]="['Products']">Product List</a></li> </ul> </div> </nav> <div class='container'> <router-outlet></router-outlet> </div> </div> `, //same as before … })
import { ROUTER_PROVIDERS, RouteConfig, ROUTER_DIRECTIVES } from 'angular2/router';
directives: [ROUTER_DIRECTIVES],
//same as before … import { ProductDetailComponent } from './products/product-detail.component'; @Component({ //same as before … }) @RouteConfig([ //same as before … { path: '/product/:id', name: 'ProductDetail', component: ProductDetailComponent } ]) //same as before …
<td>{{ product.productName }}</td>
<td> <a [routerLink]="['ProductDetail', {id: product.productId}]"> {{product.productName}} </a> </td>
import { Component, OnInit } from 'angular2/core'; import { ROUTER_DIRECTIVES } from 'angular2/router'; //same as before … @Component({ //same as before … directives: [StarComponent, ROUTER_DIRECTIVES] })
import { Component } from 'angular2/core'; import { RouteParams } from 'angular2/router'; @Component({ templateUrl: 'app/products/product-detail.component.html' }) export class ProductDetailComponent { pageTitle: string = 'Product Detail'; constructor(private _routeParams: RouteParams) { let id = +this._routeParams.get('id'); this.pageTitle += `: ${id}`; } }
<div class='panel panel-primary'> <div class='panel-heading'> {{pageTitle}} </div> <div class='panel-footer'> <a class='btn btn-default' (click)='onBack()' style='width:80px'> <i class='glyphicon glyphicon-chevron-left'></i> Back </a> </div> </div>
سپس کدهای product-detail.component.ts را به صورت ذیل تکمیل خواهیم کرد:
import { Component } from 'angular2/core'; import { RouteParams, Router } from 'angular2/router'; @Component({ templateUrl: 'app/products/product-detail.component.html' }) export class ProductDetailComponent { pageTitle: string = 'Product Detail'; constructor(private _routeParams: RouteParams, private _router: Router) { let id = +this._routeParams.get('id'); this.pageTitle += `: ${id}`; } onBack(): void { this._router.navigate(['Products']); } }
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, constraints: new { controller = "Home" } // for catch all to work, Home|About|SomeName ); // Route override to work with Angularjs and HTML5 routing routes.MapRoute( name: "NotFound", url: "{*catchall}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } }
namespace Test { internal class Person { public string FirstName { get; set; } public string LastName { get; set; } } }
using System.Text.Json.Serialization; namespace Test { [JsonSerializable(typeof(Person))] internal partial class MyJsonContext : JsonSerializerContext { } }
Person person = new() { FirstName = "Jane", LastName = "Doe" }; byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(person, MyJsonContext.Default.Person); person = JsonSerializer.Deserialize(utf8Json, MyJsonContext.Default.Person);
namespace System.Text.Json { public static class JsonSerializer { public static object? Deserialize(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerContext context) => ...; public static object? Deserialize(ReadOnlySpan<char> json, Type returnType, JsonSerializerContext context) => ...; public static object? Deserialize(string json, Type returnType, JsonSerializerContext context) => ...; public static object? Deserialize(ref Utf8JsonReader reader, Type returnType, JsonSerializerContext context) => ...; public static ValueTask<object?> DeserializeAsync(Stream utf8Json, Type returnType, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static ValueTask<TValue?> DeserializeAsync<TValue>(Stream utf8Json, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static TValue? Deserialize<TValue>(ReadOnlySpan<byte> utf8Json, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static TValue? Deserialize<TValue>(string json, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static TValue? Deserialize<TValue>(ReadOnlySpan<char> json, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static TValue? Deserialize<TValue>(ref Utf8JsonReader reader, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static string Serialize(object? value, Type inputType, JsonSerializerContext context) => ...; public static void Serialize(Utf8JsonWriter writer, object? value, Type inputType, JsonSerializerContext context) { } public static Task SerializeAsync(Stream utf8Json, object? value, Type inputType, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task SerializeAsync<TValue>(Stream utf8Json, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static byte[] SerializeToUtf8Bytes(object? value, Type inputType, JsonSerializerContext context) => ...; public static byte[] SerializeToUtf8Bytes<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static void Serialize<TValue>(Utf8JsonWriter writer, TValue value, JsonTypeInfo<TValue> jsonTypeInfo) { } public static string Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => ...; } }
using System.Text.Json.Serialization; namespace Test { [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] [JsonSerializable(typeof(Person))] internal partial class MyJsonContext : JsonSerializerContext { } }
string json = JsonSerializer.Serialize(person, MyJsonContext.Default.Person); Person person = JsonSerializer.Deserialize(json, MyJsonContext.Default.Person);
services.AddControllers().AddJsonOptions(options => options.AddContext<MyJsonContext>());
[JsonSerializable(typeof(WeatherForecast[]))] internal partial class MyJsonContext : JsonSerializerContext { } @code { private WeatherForecast[] forecasts; private static JsonSerializerOptions Options = new(JsonSerializerDefaults.Web); private static MyJsonContext Context = new MyJsonContext(Options); protected override async Task OnInitializedAsync() { forecasts = await Http.GetFromJsonAsync("sample-data/weather.json", Context.WeatherForecastArray); } }
namespace System.Net.Http.Json { public static partial class HttpClientJsonExtensions { public static Task<object?> GetFromJsonAsync(this HttpClient client, string? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<object?> GetFromJsonAsync(this HttpClient client, System.Uri? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, string? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; } public static partial class HttpContentJsonExtensions { public static Task<object?> ReadFromJsonAsync(this HttpContent content, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<T?> ReadFromJsonAsync<T>(this HttpContent content, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; } }