نظرات مطالب
معرفی کتابخانه PdfReport
- می‌تونید یک هدر سفارشی در PdfReport درست کنید. (چیزی که به همراه آن است یک هدر ساده همه منظوره است)
- می‌تونید از قالب‌های AcroForm مربوط به Open Office در هدر Pdf Report استفاده کنید.
این موارد رو در قسمت‌های بعدی توضیح می‌دم.
پاسخ به بازخورد‌های پروژه‌ها
نمایش دو جدول در یک صفحه
من این کار را مجبور شدم انجام بدم، data source اولی را به روش معمولی انجام دادم، ولی برای data source دومی مجبور شدم یک جدول را خودم به صورت دستی بسازم و بلافاصه پس از ترسیم جدول اولی رسم کنم.
کدش را که در یکی از پروژه هام استفاده کرده بودم براتون ضمیمه کردم.
مطالب
غیرمعتبر کردن توکن و یا کوکی سرقت شده در برنامه‌های مبتنی بر ASP.NET Core
چند روز قبل، یکی از کانال‌های فنی معروف یوتیوب با بیش از 15 میلیون مشترک، هک و پاک شد! که داستان آن‌را در اینجا می‌توانید پیگیری کنید. در این هک، مهاجم در سعی اول، پیشنهاد پشتیبانی مالی از شبکه را داده و در ایمیل دوم، پس از جلب اعتماد اولیه، یک فایل به ظاهر PDF مفاد قرارداد را ارسال کرده که با کلیک بر روی آن، تمام کوکی‌های یوتیوب مالک کانال، سرقت و مورد سوء استفاده قرار گرفته! در یک چنین حالتی، مهم نیست که شما اعتبارسنجی دو مرحله‌ای را فعال کرده‌اید و یا از بهترین روش‌های رمزنگاری برای امن کردن اطلاعات کوکی و یا توکن خود استفاده کرده‌اید، همینقدر که اصل محتوای کوکی و یا توکن شما در اختیار شخص دیگری قرار گیرد، می‌تواند بدون نیاز به لاگین و دانستن کلمه‌ی عبور شما، بجای شما وارد سیستم شده و تغییرات دلخواهی را اعمال کند!
بنابراین سؤال اینجاست که ما (توسعه دهندگان) چگونه می‌توانیم یک چنین حملاتی را مشکل‌تر کنیم؟ در این مطلب روشی را در جهت سعی در غیرمعتبر کردن توکن‌ها و یا کوکی‌های سرقت شده، در برنامه‌های مبتنی بر ASP.NET Core بررسی خواهیم کرد.


توسعه‌ی یک سرویس تشخیص مرورگر و سیستم عامل شخص وارد شده‌ی به سیستم

یکی از روش‌های غیرممکن کردن یک چنین حملاتی، درج مشخصات سیستم عامل و مرورگر شخص وارد شده‌ی به سیستم، در کوکی و همچنین توکن صادر شده‌ی حاصل از اعتبارسنجی موفق است. سپس زمانیکه قرار است از اطلاعات این کوکی و یا توکن در برنامه استفاده شود، این اطلاعات را با اطلاعات درخواست جاری کاربر مقایسه کرده و در صورت عدم تطابق، درخواست او را برگشت می‌زنیم. برای مثال اگر عملیات لاگین، در ویندوز انجام شده و اکنون توکن و یا کوکی حاصل، در سیستم عامل اندروید در حاصل استفاده‌است، یعنی ... این عملیات مشکوک است و باید خاتمه یابد و کاربر باید مجبور به لاگین مجدد شود و نه اعتبارسنجی خودکار بدون زحمت!
برای این منظور می‌توان از کتابخانه‌ی UA-Parser استفاده کرد و توسط آن سرویس زیر را توسعه داد:
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
using UAParser;

namespace ASPNETCore2JwtAuthentication.Services;

/// <summary>
///     To invalidate an old user's token from a new device
/// </summary>
public class DeviceDetectionService : IDeviceDetectionService
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ISecurityService _securityService;

    public DeviceDetectionService(ISecurityService securityService, IHttpContextAccessor httpContextAccessor)
    {
        _securityService = securityService ?? throw new ArgumentNullException(nameof(securityService));
        _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
    }

    public string GetCurrentRequestDeviceDetails() => GetDeviceDetails(_httpContextAccessor.HttpContext);

    public string GetDeviceDetails(HttpContext context)
    {
        var ua = GetUserAgent(context);
        if (ua is null)
        {
            return "unknown";
        }

        var client = Parser.GetDefault().Parse(ua);
        var deviceInfo = client.Device.Family;
        var browserInfo = $"{client.UA.Family}, {client.UA.Major}.{client.UA.Minor}";
        var osInfo = $"{client.OS.Family}, {client.OS.Major}.{client.OS.Minor}";
        //TODO: Add the user's IP address here, if it's a banking system.
        return $"{deviceInfo}, {browserInfo}, {osInfo}";
    }

    public string GetDeviceDetailsHash(HttpContext context) =>
        _securityService.GetSha256Hash(GetDeviceDetails(context));

    public string GetCurrentRequestDeviceDetailsHash() => GetDeviceDetailsHash(_httpContextAccessor.HttpContext);

    public string GetCurrentUserTokenDeviceDetailsHash() =>
        GetUserTokenDeviceDetailsHash(_httpContextAccessor.HttpContext?.User.Identity as ClaimsIdentity);

    public string GetUserTokenDeviceDetailsHash(ClaimsIdentity claimsIdentity)
    {
        if (claimsIdentity?.Claims == null || !claimsIdentity.Claims.Any())
        {
            return null;
        }

        return claimsIdentity.FindFirst(ClaimTypes.System)?.Value;
    }

    public bool HasCurrentUserTokenValidDeviceDetails() =>
        HasUserTokenValidDeviceDetails(_httpContextAccessor.HttpContext?.User.Identity as ClaimsIdentity);

    public bool HasUserTokenValidDeviceDetails(ClaimsIdentity claimsIdentity) =>
        string.Equals(GetCurrentRequestDeviceDetailsHash(), GetUserTokenDeviceDetailsHash(claimsIdentity),
                      StringComparison.Ordinal);

    private static string GetUserAgent(HttpContext context)
    {
        if (context is null)
        {
            return null;
        }

        return context.Request.Headers.TryGetValue(HeaderNames.UserAgent, out var userAgent)
                   ? userAgent.ToString()
                   : null;
    }
}
توضیحات:
اصل کار این سرویس در متد زیر رخ می‌دهد:
    public string GetDeviceDetails(HttpContext context)
    {
        var ua = GetUserAgent(context);
        if (ua is null)
        {
            return "unknown";
        }

        var client = Parser.GetDefault().Parse(ua);
        var deviceInfo = client.Device.Family;
        var browserInfo = $"{client.UA.Family}, {client.UA.Major}.{client.UA.Minor}";
        var osInfo = $"{client.OS.Family}, {client.OS.Major}.{client.OS.Minor}";
        //TODO: Add the user's IP address here, if it's a banking system.
        return $"{deviceInfo}, {browserInfo}, {osInfo}";
    }
در اینجا با استفاده از کتابخانه‌ی UA-Parser، سعی می‌کنیم تا جزئیات مرورگر و سیستم عامل شخص را تهیه کنیم. سپس در قسمت دیگری از این سرویس، این اطلاعات را هش می‌کنیم. از این جهت که هم حجم آن کاهش یابد و بی‌جهت کوکی و یا توکن ما را حجیم نکند و هم بررسی محتوای آن جهت شبیه سازی آن، غیرممکن شود. هر مشخصات دریافتی در حین لاگین، همواره یک هش مشخص و یکتا را دارد. به همین جهت متدهای هش کردن اطلاعات را هم در اینجا مشاهده می‌کنید. به علاوه‌ی متد HasUserTokenValidDeviceDetails که کار آن، دریافت Claim مرتبط با این اطلاعات، از کوکی و یا توکن جاری و مقایسه‌ی آن با اطلاعات Http Request جاری است. اگر این دو یکی نبودند، یعنی احتمال سوء استفاده‌ی از اطلاعات شخص، وجود دارد.


اضافه کردن اطلاعات مشخصات دستگاه کاربر به کوکی و یا توکن او

همانطور که عنوان شد، در متد HasUserTokenValidDeviceDetails، ابتدا مشخصات دستگاه موجود در کوکی و یا توکن دریافتی، استخراج می‌شود. به همین جهت نیاز است این مشخصات را دقیقا در حین لاگین موفق، به صورت یک Claim جدید، برای مثال از نوع ClaimTypes.System به مجموعه‌ی Claims کاربر اضافه کرد:
new(ClaimTypes.System, _deviceDetectionService.GetCurrentRequestDeviceDetailsHash(),
ClaimValueTypes.String, _configuration.Value.Issuer),


یکپارچه کردن DeviceDetectionService با اعتبارسنج‌های کوکی‌ها و توکن‌ها

پس از افزودن مشخصات سیستم کاربر وارد شده‌ی به سیستم، به صورت یک Claim جدید به توکن‌ها، روش اعتبارسنجی اطلاعات موجود در توکن رسیده، در رخ‌داد گردان OnTokenValidated است که امکان دسترسی به HttpContext و محتوای توکن را میسر می‌کند:
.AddJwtBearer(cfg =>
{
      cfg.Events = new JwtBearerEvents
      {
           OnTokenValidated = context =>
           {
               var tokenValidatorService = context.HttpContext.RequestServices.GetRequiredService<ITokenValidatorService>();
              return tokenValidatorService.ValidateAsync(context);
           },
       };
  });
و یا اگر از کوکی‌ها استفاده می‌کنید، معادل آن به صورت زیر است:
.AddCookie(options =>
{
    options.Events = new CookieAuthenticationEvents
    {
       OnValidatePrincipal = context =>
       {
         var cookieValidatorService = context.HttpContext.RequestServices.GetRequiredService<ICookieValidatorService>();
         return cookieValidatorService.ValidateAsync(context);
       }
    };
});

در کل تمام تغییرات مورد نیاز مرتبط را جهت یک برنامه‌ی تولید کننده‌ی JWT در اینجا و برای یک برنامه‌ی مبتنی بر کوکی‌ها در اینجا می‌توانید مشاهده کنید.
نظرات مطالب
کار با Kendo UI DataSource
- «خطا می‌گیره» برای بررسی کافی نیست. اطلاعات بیشتر
دفعه‌ی قبل هم همین رویه را تکرار کردی و پاسخی نگرفتی. چون برای رفع مشکل، اگر نتوان شرایط شما را خیلی «سریع»، «تکرار» کرد، امکان دیباگ و رفع اشکال آن هم نیست. بنابراین اگر قسمتی از یک پروژه‌ی بزرگ را اینجا عنوان کنید که کار نمی‌کند، هیچ کسی کمکی به شما نخواهد کرد (چون امکان بررسی و شبیه سازی جزئیات آن از راه دور میسر نیست).
بنابراین قبل از اینکه سؤالی را مطرح کنید، این سؤال را خودتان بپرسید:
- آیا طرف مقابل می‌تواند به «سرعت» مشکل من را شبیه سازی و «تکرار» کند؟
اگر پاسخ آن خیر است، احتمال اینکه پاسخ مناسبی را دریافت کنید، خیلی کم است.

- نیازی به تنظیم ProxyCreationEnabled = false در این قسمت از کد نیست و روش بهتری برای آن وجود دارد. اطلاعات بیشتر
- اگر قسمتی از اسکیمای JSON شما چنین شکلی را دارد:
"address":{
   "street":"test 59",
   "city":"City test",
   "post_number":"25050"
},
معادل Kendo UI Data source آن به صورت زیر خواهد بود:
columns : [
        { field: "address.street", title: "Street" },
        { field: "address.city", title: "City" },
        { field: "address.post_number", title: "Post#" }
    ]
یک مثال کامل
نظرات مطالب
افزونه فارسی به پارسی برای word 2007
چند مورد رو می‌تونید بررسی کنید
- البته نیازی نیست آن 200 و خرده‌ای مگ را دریافت کنید. علت بالا بودن حجم آن این است که 32 بیتی و 64 بیتی و غیره، همه را با هم دارد. به آدرس زیر مراجعه کنید
http://msdn.microsoft.com/en-us/netframework/aa569263.aspx
روی دکمه نصب کلیک کنید تا یک برنامه 2 مگی در اختیار شما قرار دهد. این برنامه قسمت‌های مورد نیاز سیستم شما رو از سایت مایکروسافت دریافت و نصب خواهد کرد. بنابراین حجم کمتری دارد.
- آیا این افزونه به لیست add-in ها در word‌ اضافه شده؟ (شکل سوم در این صفحه)
- event viewer ویندوز را باز کنید. در قسمت run ویندوز تایپ کنید eventvwr.msc و سپس enter (و یا به قسمت administrative tools ویندوز مراجعه کنید این برنامه را مشاهده خواهید کرد). در صفحه باز شده به قسمت applications مراجعه کنید. آیا موردی با source مساوی FarsiToParsi از نوع خطا با آیکون قرمز مشاهده می‌شود؟ اگر بله، لطفا دوبار روی آن کلیک کنید و توضیحات صفحه باز شده را اینجا ارسال کنید. از روی این خطا می‌شود مشکل را بهتر بررسی کرد.

با تشکر
نظرات مطالب
آشنایی با الگوی MVP
سلام، جناب نصیری اگه برنامه بزرگ بشه نیاز به الگوی Facade بیشتر نمیشه؟منظورم استفاده اش تو این الگو.
یه ازای همه کلاس ها باید اینترفیس تعریف کنیم؟
(حتی واسه گوگل کلمه MVP تازه است به طوری که did you mean... گوگل کلمه MVC رو پیشنهاد میکنه!)
مطالب
نمایش پیام هشدار در Blazor با استفاده از کامپوننت Alert بوت استرپ ۵

بر اساس آموزش مدیریت حالت در Blazor، قصد داریم یک سرویس پیام هشدار ساده، ولی زیبا را بوسیله کامپوننت Alert بوت استرپ ۵ ، بدون استفاده از توابع جاوا اسکریپتی، طراحی کنیم.

در ابتدا کتابخانه‌های css زیر را بوسیله LibMan به پروژه اضافه کرده و مداخل فایل‌های را  css   نیز اضافه می‌کنیم:

{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": [
    {
      "provider": "unpkg",
      "library": "bootstrap@5.0.0",
      "destination": "wwwroot/lib/bootstrap"
    },
    {
      "provider": "unpkg",
      "library": "open-iconic@1.1.1",
      "destination": "wwwroot/lib/open-iconic"
    },
   
    {
      "provider": "unpkg",
      "library": "animate.css@4.1.1",
      "destination": "wwwroot/lib/animate"
    },
    {
      "provider": "unpkg",
      "library": "bootstrap-icons@1.5.0",
      "destination": "wwwroot/lib/bootstrap-icons/"
    }
   ]
}

در ادامه کلاس سرویس پیام را  پیاده سازی و   آن‌ را با طول عمر Scoped به سیستم تزریق وابستگی‌های برنامه، معرفی میکنیم
    public enum  AlertType
    {
        Success,
        Info,
        Danger,
        Warning
    }

    public class AlertService
    {
        
        public void ShowAlert(string message, AlertType alertType,  string animate = "animate__fadeIn")
            {
             OnChange?.Invoke(message, alertType,animate);
            }

           public event Action<string,AlertType, string> OnChange;        
        }
services.AddScoped<AlertService>();

توضیحات:

در کدهای نهایی برنامه قرار است به این نحو کار نمایش Alertها را در کامپوننت‌های مختلف انجام دهیم:

@inject AlertService AlertService

@code {
    private void Success()
    {
        AlertService.ShowAlert("Success!", AlertType.Success);
    }
این کامپوننت‌ها هم الزاما در یک سلسله مراتب قرار ندارند و ارسال پارامترهای آبشاری به آن‌ها صدق نمی‌کند. به همین جهت یک سرویس Scoped را طراحی کرده‌ایم که در برنامه‌های Blazor WASM، طول عمر آن، با طول عمر برنامه یکی است؛ یعنی به صورت Singleton عمل می‌کند و در تمام کامپوننت‌ها و سرویس‌های دیگر نیز در دسترس خواهد بود. زمانیکه متد AlertService.ShowAlert فراخوانی می‌شود، سبب بروز رویداد OnChange خواهد شد و تمام گوش دهندگان به آن که در اینجا تنها کامپوننت Alert سفارشی ما است (برای مثال آن‌را در MainLayout.razor قرار می‌دهیم )، مطلع شده و بلافاصله محتوایی را نمایش می‌دهند.

کدهای کامپوننت Alert.razor

@inject AlertService AlertService
@implements IDisposable
 <style>
        .alert-show {
            display: flex;
            flex-direction: row;
           }

        .alert-hide {
            display: none;
        }
  </style>
    <div style="z-index: 5">
        <div " + "alert-show" :"alert-hide")">
            <i width="24" height="24"></i>
            <div>
                @Message
            </div>
            <button type="button" data-bs-dismiss="alert" aria-label="Close" @onclick="CloseClick"></button>
        </div>
    </div>


        @code {

            AlertType AlertType { get; set; }
            string Icon { get; set; }
            string Css { get; set; }
            string Animation { get; set; }
            private bool IsVisible { get; set; }
            private string Message { get; set; }
            System.Timers.Timer _alertTimeOutTimer;
            protected override void OnInitialized()
            {
              AlertService.OnChange += ShowAlert;
            }

            private void ShowAlert(string message, AlertType alertType, string animate)
            {
                _alertTimeOutTimer = new System.Timers.Timer
                {
                    Interval = 5000,
                    Enabled = true,
                    AutoReset = false
                };
                _alertTimeOutTimer.Elapsed += HideAlert;
                Message = message;
                switch (alertType)
                {
                    case AlertType.Success:
                        Css = "bg-success";
                        Icon = "bi-check-circle";
                        break;
                    case AlertType.Info:
                        Css = "bg-info";
                        Icon = "bi-info-circle-fill";
                        break;
                    case AlertType.Danger:
                        Css = "bg-danger";
                        Icon = "bi-exclamation-circle";
                        break;
                    case AlertType.Warning:
                        Css = "bg-warning";
                        Icon = "bi-exclamation-triangle-fill";
                        break;
                    default:
                        Css = Css;
                        break;
                }
                AlertType = alertType;
                Animation = animate;
                IsVisible = true;
                InvokeAsync(StateHasChanged);
            }
            private void HideAlert(Object source, System.Timers.ElapsedEventArgs e)
            {
                IsVisible = false;
                InvokeAsync(StateHasChanged);
                _alertTimeOutTimer.Close();
            }
            public void Dispose()
            {
                if (AlertService != null) AlertService.OnChange -= ShowAlert;
                if (_alertTimeOutTimer != null)
                {
                    _alertTimeOutTimer.Elapsed -= HideAlert;
                    _alertTimeOutTimer?.Dispose();
                }
            }
            private void CloseClick()
            {
                IsVisible = false;
                _alertTimeOutTimer.Close();
                InvokeAsync(StateHasChanged);
            }

        }
توضیحات:
همانطور که مشاهده می‌کنید، کامپوننت Alert، از سرویس تزریق شده‌ی AlertService استفاده می‌کند. بنابراین در هرجائی از برنامه که AlertService.ShowAlert فراخوانی شود، سبب بروز رویداد OnChange شده و به این ترتیب کامپوننت فوق، Alert ای را نمایش می‌دهد که البته نمایش آن به همراه یک Timeout و محو شدن خودکار نیز هست. برای استفاده از کامپوننت Alert.razor، آن‌را در صفحه اصلی MainLayout یا هرجای دلخواهی قرار می‌دهیم:
<div>
        <Alert></Alert>
و سپس با تزریق AlertService در کامپوننت مورد نظر (که محل آن مهم نیست) و اجرای متد ShowAlert آن به‌همراه پارامترهای آن، پیام هشداری را که توسط MainLayout نمایش داده می‌شود، مشاهده خواهیم کرد.

دریافت کدهای کامل برنامه:  BlazorBootstrapAlert.zip