نظرات مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت ششم - تکمیل مستندات محافظت از API
این روش برای نوشتن دلیل شکست عملیات در response body، با بازنویسی متد HandleChallengeAsync، آزمایش شده:
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Options;

namespace OpenAPISwaggerDoc.Web.Authentication;

public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    private string _failReason;

    public BasicAuthenticationHandler(
        IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        ISystemClock clock)
        : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.ContainsKey("Authorization"))
        {
            _failReason = "Missing Authorization header";
            return Task.FromResult(AuthenticateResult.Fail(_failReason));
        }

        try
        {
            var authenticationHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
            var credentialBytes = Convert.FromBase64String(authenticationHeader.Parameter);
            var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':');
            var username = credentials[0];
            var password = credentials[1];

            if (string.Equals(username, "DNT", StringComparison.Ordinal) &&
                string.Equals(password, "123", StringComparison.Ordinal))
            {
                var claims = new[] { new Claim(ClaimTypes.NameIdentifier, username) };
                var identity = new ClaimsIdentity(claims, Scheme.Name);
                var principal = new ClaimsPrincipal(identity);
                var ticket = new AuthenticationTicket(principal, Scheme.Name);
                return Task.FromResult(AuthenticateResult.Success(ticket));
            }

            _failReason = "Invalid username or password";
            return Task.FromResult(AuthenticateResult.Fail(_failReason));
        }
        catch
        {
            _failReason = "Invalid Authorization header";
            return Task.FromResult(AuthenticateResult.Fail(_failReason));
        }
    }

    protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        await base.HandleChallengeAsync(properties);
        if (Response.StatusCode == StatusCodes.Status401Unauthorized &&
            !string.IsNullOrWhiteSpace(_failReason))
        {
            Response.Headers.Add("WWW-Authenticate", _failReason);
            Response.ContentType = "application/json";
            await WriteProblemDetailsAsync(_failReason);
        }
    }

    private Task WriteProblemDetailsAsync(string detail)
    {
        var problemDetails = new ProblemDetails { Detail = detail, Status = Context.Response.StatusCode };
        var result = new ObjectResult(problemDetails)
                     {
                         ContentTypes = new MediaTypeCollection(),
                         StatusCode = problemDetails.Status,
                         DeclaredType = problemDetails.GetType(),
                     };
        var executor = Context.RequestServices.GetRequiredService<IActionResultExecutor<ObjectResult>>();
        var routeData = Context.GetRouteData() ?? new RouteData();
        var actionContext = new ActionContext(Context, routeData, new ActionDescriptor());
        return executor.ExecuteAsync(actionContext, result);
    }
}
نظرات مطالب
پشتیبانی توکار از انجام کارهای پس‌زمینه در ASP.NET Core 2x
یک نکته‌ی تکمیلی: دات نت 6 و معرفی یک تایمر Async جدید

در این مطلب برای انجام کارهای پس زمینه‌ای متناوب و async، مجبور به اختراع تایمرهای خاصی شدیم که در دات نت 6، روش بهتری برای انجام آن ارائه شده‌است. تا پیش از دات نت 6، تایمرهای زیر در فضاهای نام مختلفی تعریف شده‌اند:

- System.Threading.Timer
- System.Timers.Timer
- System.Windows.Forms.Timer
- System.Web.UI.Timer
- System.Windows.Threading.DispatcherTimer

طراحی تمام این تایمرها مبتنی بر callbackها است و رخ‌دادهایی که توسط تایمر، در زمان مشخصی صادر می‌شوند. این تایمرها مشکلات زیر را به همراه دارند:
1- متد callback فراخوانی شده async نیست (زمانی طراحی شده بودند که نوع Task، هنوز وجود خارجی نداشت).
2- اگر درون callback خطایی رخ‌دهد، خاموش سازی تایمر نیاز به عملیات اضافه‌تری دارد.
3- اگر عملیات درون یک callback هنوز به پایان نرسیده باشد، ممکن است این callback مجددا فراخوانی شود.

برای رفع تمام این مشکلات، تایمر جدیدی به نام PeriodicTimer به دات نت 6 اضافه شده‌است که این مزایا را به همراه دارد:
1- تمام async است.
2- تنها یک جریان کاری مشخص را دارد که با فراخوانی دستی متد WaitForNextTickAsync آن، به میزان بازه‌ی زمانی مشخص شده، صبر خواهد شد. وجود تنها یک جریان کاری، مشکلات 2 و 3 تایمرهای قبلی را رفع می‌کند.

یک مثال:
private async Task DoTaskAsync()
{
   using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));   
   while (await timer.WaitForNextTickAsync())
   {
      Console.WriteLine($"Firing at {DateTime.Now}");
   }   
}
این تایمر async، هر 5 ثانیه یکبار، کدهای بدنه‌ی حلقه را اجرا می‌کند.

اگر خواستیم این تایمر، پس از 20 ثانیه به طور کامل متوقف شود، روش کار به صورت زیر است که توسط یک CancellationTokenSource که به عنوان پارامتر متد WaitForNextTickAsync ارسال می‌شود، قابل پیاده سازی است:
private async Task DoTaskAsync()
{
   try
   {
      var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));

      using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));   
      while (await timer.WaitForNextTickAsync(cts.Token))
      {
         Console.WriteLine($"Firing at {DateTime.Now}");
      }   
   }
   catch (OperationCanceledException)
   {
       Console.WriteLine("Operation cancelled");
   }
}
این خاتمه‌ی خودکار، با صدور یک OperationCancelled exception رخ خواهد داد و یا حتی می‌توان متد ()cts.Cancel را نیز در صورت نیاز به صورت دستی در داخل حلقه فراخوانی کرد تا عملیات خاتمه یابد.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 13 - معرفی View Components
نکته تکمیلی :
ایجاد منوهای چند سطحی با استفاده از ViewComponent تا N سطح
کلاس Entity :
public class Navbar
    {
        public int Id { get; set; }
        public string Title { get; set; }

        public int? ParentId { get; set; }
        public virtual Navbar Parent { get; set; }
        public bool IsActive { get; set; }
        public bool HasChiled { get; set; }
        public bool IsMegaMenu { get; set; }
        public PageGroup PageGroup { get; set; }
        public string Url { get; set; }
        public bool OpenNewPage { get; set; }

        public virtual ICollection<Navbar> Children { get; set; }
    }
کلاس ViewComponent :
    public class TopNavbar : ViewComponent
    {
        private readonly DbSet<Navbar> _navbars;
        private readonly AppDbContext _dbContext;

        public TopNavbar(AppDbContext dbContext)
        {
            _dbContext = dbContext;
            _navbars = _dbContext.Set<Navbar>();
        }
        public async Task<IViewComponentResult> InvokeAsync()
        {
            var navbars = await _navbars.Include(p=>p.Parent).Include(x=>x.Children).OrderBy(x=>x.ParentId).ToListAsync();
            return View(viewName: "~/Views/Shared/Components/NavbarViewComponent/_Menu.cshtml", navbars);
        }
    }
فراخوانی viewcomponent در Layout.cshtml
 <ul class="menu">
                <li>
                    <a href="Index_demo6.html"><i class="menu_icon_wrapper fal fa-home-lg-alt"></i>صفحه اصلی</a>
                </li>
                @await Component.InvokeAsync("TopNavbar");                
 </ul>
Menu.cshtml_ :
@using TR.Context.Entities
@using Microsoft.AspNetCore.Html
@model IEnumerable<TR.Context.Entities.Navbar>


@foreach (var menu in Model.Where(x => x.Parent == null))
{

    <li class="@(menu.HasChiled ? "has_sub narrow" : "")">
        <a href="#">@menu.Title</a>
        @if (menu.HasChiled)
        {
            <div class="second">
                <div class="inner">
                    <ul>
                        @foreach (var menuChild in menu.Children)
                        {
                        <partial name="~/Views/Shared/Components/NavbarViewComponent/_SubMenu.cshtml" model="menuChild" />
                        }
                    </ul>
                </div>
            </div>
        }
    </li>
}
پارشیال ویو SubMenu.cshtml_
@model TR_.Context.Entities.Navbar

<li class="@(Model.HasChiled ? "sub":"")">
    <a href="#">
        @if (Model.Children.Any())
        {<i class="q_menu_arrow fal fa-angle-left"></i>}
        @Model.Title
    </a>
    @if (Model.Children.Any())
    {
        <ul>
            @foreach (var menuChild in Model.Children)
            {
                <partial name="~/Views/Shared/Components/NavbarViewComponent/_SubMenu.cshtml" model="menuChild" />
            }
        </ul>
    }
</li>
با این خروجی :

نظرات مطالب
Blazor 5x - قسمت ششم - مبانی Blazor - بخش 3 - چرخه‌های حیات کامپوننت‌ها
یک نکته‌ی تکمیلی: تنظیم پویای عنوان صفحات در برنامه‌های Blazor

برای تنظیم پویای عنوان یک صفحه‌ی وب، نیاز است با DOM API مرورگر به صورت مستقیم کار کرد. برای مثال فایل wwwroot\main.js را که مدخل آن به کامپوننت Host_ و یا صفحه‌ی index.html اضافه می‌شود، به صورت زیر تکمیل می‌کنیم:
window.JsFunctionHelper = {
  blazorSetTitle: function (title) {
    document.title = title;
  }
};
اکنون می‌خواهیم این متد جاوااسکریپتی را که مستقیما با شیء document کار می‌کند، در کامپوننت جدید Client\Shared\PageTitle.razor استفاده کنیم:
@inject IJSRuntime JSRuntime

@code
{
   [Parameter]
   public string Title { get; set; }

   protected override async Task OnParametersSetAsync()
   {
      await JSRuntime.InvokeVoidAsync("JsFunctionHelper.blazorSetTitle", Title);
   }
}
در اینجا کامپوننت جدیدی تعریف شده‌است که به محض تنظیم مقدار پارامتر عنوان آن، سبب فراخوانی متد جاوا اسکریپتی blazorSetTitle می‌شود. برای نمونه روش استفاده‌ی از آن در کامپوننت Counter، جهت نمایش عنوانی پویا، به محض تغییر مقدار شمارشگر، به صورت زیر می‌تواند باشد:
@page "/counter"

<PageTitle Title="@GetPageTitle()" />

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }

    private string GetPageTitle() => $"Counter ({currentCount})";
}
این روش در برنامه‌های Blazor Server کار نخواهد کرد و در حین فراخوانی متد InvokeVoidAsync یک NullReferenceException مشاهده می‌شود؛ چون این نوع برنامه‌های Blazor Server به همراه یک مرحله‌ی pre-render در سمت سرور هستند که ابتدا، کار تهیه‌ی HTML ای را که باید به سمت مرورگر ارسال کنند، به پایان می‌رسانند. در این مرحله خبری از DOM نیست که بتوان به آن دسترسی یافت و تغییری را در آن ایجاد کرد.
برای رفع این مشکل همانطور که در مطلب جاری نیز عنوان شد، باید از روال رویدادگردان OnAfterRenderAsync استفاده کرد. در این حالت کدهای کامپوننت PageTitle.razor به صورت زیر تغییر می‌کنند:
@inject IJSRuntime JSRuntime

@code
{
   [Parameter]
   public string Title { get; set; }

   protected override async Task OnAfterRenderAsync(bool firstRender)
   {
      await JSRuntime.InvokeVoidAsync("JsFunctionHelper.blazorSetTitle", Title);
   }
}
روال رویدادگردان OnAfterRenderAsync پس از اینکه کار بارگذاری و تشکیل کامل DOM در مرورگر انجام شد، فراخوانی می‌شود. به همین جهت دیگر دسترسی به شیء document.title، سبب بروز یک NullReferenceException نخواهد شد.


یک نکته: قرار است در Blazor 6x، کامپوننت‌های جدید Title، Link و Meta جهت تنظیم اطلاعات تگ head صفحه، به صورت استاندارد اضافه شوند:
<Title Value="@title" />
<Meta name="description" content="Modifying the head from a Blazor component." />
<Link href="main.css" rel="stylesheet" />
نظرات مطالب
Blazor 5x - قسمت دهم - مبانی Blazor - بخش 7 - مسیریابی
تعدادی پرسش و پاسخ تکمیلی مباحث مسیریابی

چگونه می‌توان DateTime را به عنوان پارامتر مسیریابی ارسال کرد؟
Blazor از پارامترهایی از نوع DateTime، در حین مسیریابی پشتیبانی می‌کند؛ با این شرط که قید datetime در حین مسیریابی صریحا ذکر شود:
@page "/route/{parameter:datetime}"
یک مثال: کامپوننت index.razor که سبب هدایت به سمت کامپوننت دیگری به همراه ارسال پارامتری از نوع DateTime می‌شود:
@page "/"
@inject NavigationManager NavManager
<button @onclick="CurrentTime">Current Time</button>
@code {
    public void CurrentTime()
    {
        NavManager.NavigateTo("/time/" + DateTime.Now);
    }
}
و کامپوننت Time.razor که قادر است این DateTime را دریافت کرده و به یک پارامتر، Bind کند:
@page "/time/{param:datetime}"
<h3>Time</h3>
<p>@Param</p>
@code {
    [Parameter]
    public DateTime Param { get; set; }
}


چگونه می‌توان به عنوان صفحه‌ی جاری دسترسی یافت؟
برای اینکار نیاز است از JavaScript interop استفاده کرد. ابتدا برای مثال تابع عمومی getTitle را که بر اساس DOM API مرورگر کار می‌کند، تهیه می‌کنیم:
window.getTitle = () => {
   return document.title;
};
سپس می‌توان از آن در یک کامپوننت Blazor به صورت زیر استفاده کرد:
@page "/"
@inject IJSRuntime jsRuntime
<h2>Page Title: @Title</h2>
<button class="btn btn-primary" @onclick="@GetTitle">Get Title</button>
@code {
public string Title = "";
    
    public async void GetTitle()
    {
            Title = await jsRuntime.InvokeAsync<string>("getTitle");
     }
}

چگونه می‌توان مسیری را از طریق کدهای برنامه در یک برگه‌ی مجزای مرورگر باز کرد؟
در اینجا نیز می‌توان با استفاده از JavaScript interop، متد استاندارد open مرورگر را فراخوانی کرد و پارامتر اول آن‌را به url مدنظر و پارامتر بعدی آن‌را به blank_ تنظیم کرد تا مرورگر آدرس درخواستی را در یک برگه‌ی جدید باز کند:
@inject IJSRuntime jsRuntime

<button @onclick="NavigateToNewTab">New Tab Navigation</button>
@code {
public async Task NavigateToNewTab()
    {
        string url = "/counter";
        await jsRuntime.InvokeAsync<object>("open", url, "_blank");
    }
}
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا
سلام توی پروژه من سطح دسترسی‌ها زیاده وقتی میخوام یه توکن تولید کنم حجم توکن خیلی بالا میره چطور این مشکل را رفع کنم

اینم کد تولید توکنه

 public async Task<string> GenerateTokenAsync(ApplicationUser User)
        {
            var secretKey = Encoding.UTF8.GetBytes(_siteSettings.JwtSettings.SecretKey);
            var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature);

            var encrytionKey = Encoding.UTF8.GetBytes(_siteSettings.JwtSettings.EncrypKey);
            var encryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(encrytionKey), SecurityAlgorithms.Aes128KW, SecurityAlgorithms.Aes128CbcHmacSha256);
            


            var tokenDescriptor = new SecurityTokenDescriptor()
            {
                Issuer = _siteSettings.JwtSettings.Issuer,
                Audience = _siteSettings.JwtSettings.Audience,
                IssuedAt = DateTime.Now,
                NotBefore = DateTime.Now.AddMinutes(_siteSettings.JwtSettings.NotBeforeMinutes),
                Expires = DateTime.Now.AddMinutes(_siteSettings.JwtSettings.ExpirationMinutes),
                SigningCredentials = signingCredentials,
                Subject = new ClaimsIdentity(await GetClaimsAsync(User)),
                EncryptingCredentials = encryptingCredentials,
            };

            var tokenHandler = new JwtSecurityTokenHandler();
            var securityToken = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(securityToken);
        }


        public async Task<IEnumerable<Claim>> GetClaimsAsync(ApplicationUser User)
        {
            var Claims = new List<Claim>()
            {
                new Claim(ClaimTypes.Name,User.UserName),
                new Claim(ClaimTypes.NameIdentifier,User.Id),
                new Claim(ClaimTypes.MobilePhone,User.PhoneNumber),
                new Claim(new ClaimsIdentityOptions().SecurityStampClaimType,User.SecurityStamp),
                new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString())
            };
            var Roles = _roleManager.Roles.ToList();
            foreach (var item in Roles)
            {
                var RoleClaims = await _roleManager.FindClaimsInRole(item.Id);
                foreach (var claim in RoleClaims.Claims)
                {
                    Claims.Add(new Claim(ConstantPolicies.DynamicPermissionClaimType, claim.ClaimValue));
                }
            }
            foreach (var item in Roles)
                Claims.Add(new Claim(ClaimTypes.Role, item.Name));

            return Claims;
        }

توی پروژه ما backendبا .netoreو frontهم با react هست 

نظرات مطالب
رمزنگاری JWT و افزایش امنیت آن در ASP.NET Core
یک نکته‌ی تکمیلی: نکته امنیتی در هنگام استفاده از توکن ها

هنگامیکه کاربری اطلاعات خود را ویرایش میکند، معمولا یک ویوو مدل را از ورودی دریافت میکنیم و داده‌های آن کاربر را بر اساس آی‌دی که درون ویوو مدل ارسال شده‌است، ویرایش میکنیم. اما در این حالت کاربر میتواند با تغییر آیدی ارسالی در ویوو مدل، اطلاعات سایر کاربران را نیز تغییر دهد! برای جلوگیری از این کار میتوان به روش زیر عمل کرد. ابتدا در هنگام ساخت توکن، آیدی کاربر و یک امضا Signature (میتوان از یک GUID استفاده کرد) را در توکن نگهداری میکنیم و سپس توکن را به روش JWE رمزنگاری میکنیم تا اطلاعات توکن قابل مشاهده نباشد و در هنگام اعتبارسنجی توکن، امضای کاربر را با امضای درون توکن مقایسه میکنیم. اگر با هم تفاوت داشته باشند، به معنای آن است که توکن منقضی شده و قابل استفاده نیست. در هربار که کاربر درخواست توکنی را میدهد، باید امضای کاربر را تغییر داده و یک امضای جدید را برای او ثبت کنیم.
سپس در هنگام اجرای اکشن مورد نظر، آیدی درون توکن و آیدی ارسالی جهت ویرایش اطلاعات را بررسی خواهیم کرد. اگر این دو با هم همخوانی نداشته باشند، اجازه‌ی اجرای اکشن مورد نظر را به او نخواهیم داد و سپس امضای کاربر را تغییر میدهیم تا توکن منقضی شود و یک استثناء را صادر میکنیم.
برای پیاده سازی، ابتدا یک کلاس را برای بررسی مشخصات توکن و آیدی ارسالی میسازیم.
    public interface IJwtService
    {
        Task CheckId(int id, ClaimsPrincipal claimsPrincipal);
    }
    public class JwtService : IJwtService
    {
        private readonly IUserService _userService;

        public JwtService(IUserService userService)
        {
            _userService = userService;
        }

        public async Task CheckId(int id, ClaimsPrincipal claimsPrincipal)
        {
            var jwtId = Convert.ToInt32(claimsPrincipal.Identity.FindFirstValue(ClaimTypes.NameIdentifier));
            if (jwtId != id)
            {
                var user = _userService.GetById(jwtId);
                user.SecurityStamp = Guid.NewGuid();
                await _userService.UpdateAsync(user);
                throw new Exception("You are unauthorized to access this resource.");
            }
        }
    }
و نحوه‌ی استفاده‌ی از آن در کنترلر و اکشن مورد نظر:
private readonly IJwtService _jwtService;
private readonly IUserService _userService;
public UserController(IJwtService jwtService, IUserService userService)
        {
            _jwtService = jwtService;
            _userService = userService;
        }

        [HttpPut("Update")]
        public async Task<IActionResult> Update(UserEditViewModel editViewModel, CancellationToken cancellationToken)
        {
            _jwtService.CheckId(editViewModel.Id, HttpContext.User);
            await _userService.Update(editViewModel, cancellationToken);
        }
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 18 - کار با ASP.NET Web API
با توجه به مطالب مطرح شده در متن فوق و  نحوه استفاده از ViewModel در ASP.NET MVC و همچنین توصیه‌هایی که در رابطه با آدرس‌دهی صحیح WebApiها وجود دارد (استفاده از اسم جمع، استفاده از اسم به جای فعل و ...)، در رابطه با آدرس‌دهی صحیح برای تامین اطلاعات مورد نیاز Viewها  (در حالت ویرایش یا افزودن)، در سمت کلاینت که استفاده کننده آن می‌تواند یک کامپوننت Angular یا هر نوع دیگری باشد آیا دوستان نظر و Best Practice دارند؟
به طور مثال برای ویرایش و افزودن یک محصول به صورت زیر عمل می‌کنیم:
   //ViewModels

    public class CustomListItem
    {
        public int Id { get; set; }
        public string Text { get; set; }
    }

    public class ProductAddGetViewModel
    {
        public IEnumerable<CustomListItem> Categories { get; set; }
        public IEnumerable<CustomListItem> Groups { get; set; }
    }

    public class ProductAddViewModel
    {
        public string Name { get; set; }
        public bool IsActive { get; set; }

        public int CategoryId { get; set; }
        public int GroupId { get; set; }
    }

    public class ProductEditGetViewModel
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public IEnumerable<CustomListItem> Categories { get; set; }
        public IEnumerable<CustomListItem> Groups { get; set; }
    }

    public class ProductEditViewModel
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public int CategoryId { get; set; }
        public int GroupId { get; set; }
    }
و
    // ProductsController - ApiControler

    // GET: api/products/views/add
    [HttpGet("views/add")]
    public async Task<IActionResult> GetAdd()
    {
        ProductAddGetViewModel model = await _productService.GetAddModelAsync();
        return Ok(model)
    }

    // POST: api/products
    [HttpPost]
    public async Task<IActionResult> Add(ProductAddViewModel model)
    {
        ...
    }


    // GET: api/products/5/views/edit
    [HttpGet("{id}/views/edit")]
    public async Task<IActionResult> GetEdit(int id)
    {
        ProductEditGetViewModel model = await _productService.GetEditModelAsync(id);
        return Ok(model)
    }

    // PUT: api/products/5
    [HttpPut("{id}")]
    public async Task<IActionResult> Edit(int id, ProductEditViewModel model)
    {
        ...
    }
با توجه به اینکه حالت فوق، احتمالاً دو متد از چند متد مورد استفاده می‌باشد، آیا دوستان درباره متدهای GetAdd , GetEdit و همچنین آدرس‌دهی صحیح این نوع متدها که قرار است از سمت کلاینت فراخوانی شود نظری دارند؟
پ.ن: درباره نامگذاری بهتر ViewModelها هم اگر نظری هست ممنون میشم بیان شود.
نظرات مطالب
از سرگیری مجدد، لغو درخواست و سعی مجدد دریافت فایل‌های حجیم توسط HttpClient
سلام؛ اگر قصد داشته باشیم از طریق Angular 2  دانلود رو فراخوانی کنیم ، به شکلی که یک فایل حجیم بر روی مرورگر کاربر دانلود بشه ، پیاده سازی به چه صورت خواهد بود ، الان شما در سمت سرور یک حلقه while دارید که محتوی رو می‌خونه ، این یعنی در هر اجرای حلقه من باید محتوی خوانده شده رو return کنم به کلاینت و دوباره با آفست جدید فراخوانی کنم؟
نحوه فراخوانی من به شکل زیر هست که با FileStreamResult برگشتی از سمت سرور به درستی کار میکنه ولی خب همه فایل رو یکجا برمی گردونه :
 downloadOrder(orderId: number , userId : string) {

    this._http.get(this._config.getApiURI() + '/Download/productfile/' + orderId + '/' + userId, { observe: 'blob'})
      .subscribe(
        (data) => {
          if(this._functionService.isNullOrEmpty(data.body)){
            this._snackBarService.error('فایل پیدا نشد');
            return;
          }
          var contentType = data.body.type || "application/octet-stream";

          var fileInfo = JSON.parse(data.headers.get('FileInfo'));          
          var blob = new Blob([data.body], { type: contentType });
          var url = window.URL.createObjectURL(blob);
          var anchor = document.createElement("a");
          anchor.setAttribute("href", url)
          anchor.setAttribute("download", fileInfo.fileName + fileInfo.fileType);
          anchor.setAttribute("target", "_blank");
          document.body.appendChild(anchor);
          anchor.click();
          setTimeout(function () {
            document.body.removeChild(anchor);
            window.URL.revokeObjectURL(url);
          }, 200);
        },
        error => {
          this._snackBarService.error(error);
        });
  }
ولی حالا که قراره فایل رو در چندین مرحله و با حلقه while برگردونیم ، با این سناریو یعنی باید از طرف کلاینت چندین بار فراخوانی باشه ، مگر اینکه کلا یک window جدید باز کنیم  (که احتمالا با مشکل popup blocker مواجه میشیم) و ... آیا باید در سمت سرور بایت‌های خوانده شده رو تو Response بنویسیم ...
نحوه پیاده سازی کد پایین به چه شکل میشه
             while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)  
                    {                        
                        await Response.Body.WriteAsync(buffer);                        
                        //return File(buffer, GetContentType(fullPath), newFileName, true);                        
                        //return new FileStreamResult(stream,"application/octet-stream");
                    }
نظرات مطالب
اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
با تشکر . یعنی موقعی که توکن ایجاد میشود از claimها هم برای ایجاد آن استفاده میشود؟در اینجا
  private async Task<(string AccessToken, IEnumerable<Claim> Claims)> createAccessTokenAsync(User user)
        {
            var claims = new List<Claim>
            {
                // Unique Id for all Jwt tokes
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString(), ClaimValueTypes.String, _configuration.Value.Issuer),
                // Issuer
                new Claim(JwtRegisteredClaimNames.Iss, _configuration.Value.Issuer, ClaimValueTypes.String, _configuration.Value.Issuer),
                // Issued at
                new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64, _configuration.Value.Issuer),
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), ClaimValueTypes.String, _configuration.Value.Issuer),
                new Claim(ClaimTypes.Name, user.Username, ClaimValueTypes.String, _configuration.Value.Issuer),
                new Claim("DisplayName", user.DisplayName, ClaimValueTypes.String, _configuration.Value.Issuer),
                // to invalidate the cookie
                new Claim(ClaimTypes.SerialNumber, user.SerialNumber, ClaimValueTypes.String, _configuration.Value.Issuer),
                // custom data
                new Claim(ClaimTypes.UserData, user.Id.ToString(), ClaimValueTypes.String, _configuration.Value.Issuer)
            };

           // add userclaims for permission
            var clmsuser= await _rolesService.FindUserClaimesAsync(user.Id);
            foreach (var cls in clmsuser)
            {
                claims.Add(new Claim(cls.ClaimType,cls.ClaimValue,ClaimValueTypes.String,_configuration.Value.Issuer));
            }
           
            // add roles
            var roles = await _rolesService.FindUserRolesAsync(user.Id);
            foreach (var role in roles)
            {
                claims.Add(new Claim(ClaimTypes.Role, role.Name, ClaimValueTypes.String, _configuration.Value.Issuer));
            }

            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.Value.Key));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var now = DateTime.UtcNow;
            var token = new JwtSecurityToken(
                issuer: _configuration.Value.Issuer,
                audience: _configuration.Value.Audience,
                claims: claims,
                notBefore: now,
                expires: now.AddMinutes(_configuration.Value.AccessTokenExpirationMinutes),
                signingCredentials: creds);
            return (new JwtSecurityTokenHandler().WriteToken(token), claims);
        }

و هر تغییر در claimها در سمت کاربر برابر میشود با  برگشت توکن دستکاری شده از سمت سرور و مسدود شدن دسترسی؟