مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت سیزدهم- فعالسازی اعتبارسنجی دو مرحله‌ای
در انتهای «قسمت دوازدهم- یکپارچه سازی با اکانت گوگل»، کار «اتصال کاربر وارد شده‌ی از طریق یک IDP خارجی به اکانتی که هم اکنون در سطح IDP ما موجود است» انجام شد. اما این مورد یک مشکل امنیتی را هم ممکن است ایجاد کند. اگر IDP ثالث، ایمیل اشخاص را تعیین اعتبار نکند، هر شخصی می‌تواند ایمیل دیگری را بجای ایمیل اصلی خودش در آنجا ثبت کند. به این ترتیب یک مهاجم می‌تواند به سادگی تنها با تنظیم ایمیل کاربری مشخص و مورد استفاده‌ی در برنامه‌ی ما در آن IDP ثالث، با سطح دسترسی او فقط با دو کلیک ساده به سایت وارد شود. کلیک اول، کلیک بر روی دکمه‌ی external login در برنامه‌ی ما است و کلیک دوم، کلیک بر روی دکمه‌ی انتخاب اکانت، در آن اکانت لینک شده‌ی خارجی است.
برای بهبود این وضعیت می‌توان مرحله‌ی دومی را نیز به این فرآیند لاگین افزود؛ پس از اینکه مشخص شد کاربر وارد شده‌ی به سایت، دارای اکانتی در IDP ما است، کدی را به آدرس ایمیل او ارسال می‌کنیم. اگر این ایمیل واقعا متعلق به این شخص است، بنابراین قادر به دسترسی به آن، خواندن و ورود آن به برنامه‌ی ما نیز می‌باشد. این اعتبارسنجی دو مرحله‌ای را می‌توان به عملیات لاگین متداول از طریق ورود نام کاربری و کلمه‌ی عبور در IDP ما نیز اضافه کرد.


تنظیم میان‌افزار Cookie Authentication

مرحله‌ی اول ایجاد گردش کاری اعتبارسنجی دو مرحله‌ای، فعالسازی میان‌افزار Cookie Authentication در برنامه‌ی IDP است. برای این منظور به کلاس Startup آن مراجعه کرده و AddCookie را اضافه می‌کنیم:
namespace DNT.IDP
{
    public class Startup
    {
        public const string TwoFactorAuthenticationScheme = "idsrv.2FA";

        public void ConfigureServices(IServiceCollection services)
        {
            // ...

            services.AddAuthentication()
                .AddCookie(authenticationScheme: TwoFactorAuthenticationScheme)
                .AddGoogle(authenticationScheme: "Google", configureOptions: options =>
                {
                    options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                    options.ClientId = Configuration["Authentication:Google:ClientId"];
                    options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
                });
        }


اصلاح اکشن متد Login برای هدایت کاربر به صفحه‌ی ورود اطلاعات کد موقتی

تا این مرحله، در اکشن متد Login کنترلر Account، اگر کاربر، اطلاعات هویتی خود را صحیح وارد کند، به سیستم وارد خواهد شد. برای لغو این عملکرد پیش‌فرض، کدهای HttpContext.SignInAsync آن‌را حذف کرده و با Redirect به اکشن متد نمایش صفحه‌ی ورود کد موقتی ارسال شده‌ی به آدرس ایمیل کاربر، جایگزین می‌کنیم.
namespace DNT.IDP.Controllers.Account
{
    [SecurityHeaders]
    [AllowAnonymous]
    public class AccountController : Controller
    {
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Login(LoginInputModel model, string button)
        {
    // ... 

            if (ModelState.IsValid)
            {
                if (await _usersService.AreUserCredentialsValidAsync(model.Username, model.Password))
                {
                    var user = await _usersService.GetUserByUsernameAsync(model.Username);
                    
                    var id = new ClaimsIdentity();
                    id.AddClaim(new Claim(JwtClaimTypes.Subject, user.SubjectId));
                    await HttpContext.SignInAsync(scheme: Startup.TwoFactorAuthenticationScheme, principal: new ClaimsPrincipal(id));

                    await _twoFactorAuthenticationService.SendTemporaryCodeAsync(user.SubjectId);

                    var redirectToAdditionalFactorUrl =
                        Url.Action("AdditionalAuthenticationFactor",
                            new
                            {
                                returnUrl = model.ReturnUrl,
                                rememberLogin = model.RememberLogin
                            });                   

                    // request for a local page
                    if (Url.IsLocalUrl(model.ReturnUrl))
                    {
                        return Redirect(redirectToAdditionalFactorUrl);
                    }

                    if (string.IsNullOrEmpty(model.ReturnUrl))
                    {
                        return Redirect("~/");
                    }

                    // user might have clicked on a malicious link - should be logged
                    throw new Exception("invalid return URL");
                }

                await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials"));
                ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage);
            }

            // something went wrong, show form with error
            var vm = await BuildLoginViewModelAsync(model);
            return View(vm);
        }
- در این اکشن متد، ابتدا مشخصات کاربر، از بانک اطلاعاتی بر اساس نام کاربری او، دریافت می‌شود.
- سپس بر اساس Id این کاربر، یک ClaimsIdentity تشکیل می‌شود.
- در ادامه با فراخوانی متد SignInAsync بر روی این ClaimsIdentity، یک کوکی رمزنگاری شده را با scheme تعیین شده که با authenticationScheme تنظیم شده‌ی در کلاس آغازین برنامه تطابق دارد، ایجاد می‌کنیم.
 await HttpContext.SignInAsync(scheme: Startup.TwoFactorAuthenticationScheme, principal: new ClaimsPrincipal(id));
سپس کد موقتی به آدرس ایمیل کاربر ارسال می‌شود. برای این منظور سرویس جدید زیر را به برنامه اضافه کرده‌ایم:
    public interface ITwoFactorAuthenticationService
    {
        Task SendTemporaryCodeAsync(string subjectId);
        Task<bool> IsValidTemporaryCodeAsync(string subjectId, string code);
    }
- کار متد SendTemporaryCodeAsync، ایجاد و ذخیره‌ی یک کد موقتی در بانک اطلاعاتی و سپس ارسال آن به کاربر است. البته در اینجا، این کد در صفحه‌ی Console برنامه لاگ می‌شود (یا هر نوع Log provider دیگری که برای برنامه تعریف کرده‌اید) که می‌توان بعدها آن‌را با کدهای ارسال ایمیل جایگزین کرد.
- متد IsValidTemporaryCodeAsync، کد دریافت شده‌ی از کاربر را با نمونه‌ی موجود در بانک اطلاعاتی مقایسه و اعتبار آن‌را اعلام می‌کند.


ایجاد اکشن متد AdditionalAuthenticationFactor و View مرتبط با آن

پس از ارسال کد موقتی به کاربر، کاربر را به صورت خودکار به اکشن متد جدید AdditionalAuthenticationFactor هدایت می‌کنیم تا این کد موقتی را که به صورت ایمیل (و یا در اینجا با مشاهده‌ی لاگ برنامه)، دریافت کرده‌است، وارد کند. همچنین returnUrl را نیز به این اکشن متد جدید ارسال می‌کنیم تا بدانیم پس از ورود موفق کد موقتی توسط کاربر، او را باید در ادامه‌ی این گردش کاری به کجا هدایت کنیم. بنابراین قسمت بعدی کار، ایجاد این اکشن متد و تکمیل View آن است.
ViewModel ای که بیانگر ساختار View مرتبط است، چنین تعریفی را دارد:
using System.ComponentModel.DataAnnotations;

namespace DNT.IDP.Controllers.Account
{
    public class AdditionalAuthenticationFactorViewModel
    {
        [Required]
        public string Code { get; set; }

        public string ReturnUrl { get; set; }

        public bool RememberLogin { get; set; }
    }
}
که در آن، Code توسط کاربر تکمیل می‌شود و دو گزینه‌ی دیگر را از طریق مسیریابی و هدایت به این View دریافت خواهد کرد.
سپس اکشن متد AdditionalAuthenticationFactor در حالت Get، این View را نمایش می‌دهد و در حالت Post، اطلاعات آن‌را از کاربر دریافت خواهد کرد:
namespace DNT.IDP.Controllers.Account
{
    public class AccountController : Controller
    {
        [HttpGet]
        public IActionResult AdditionalAuthenticationFactor(string returnUrl, bool rememberLogin)
        {
            // create VM
            var vm = new AdditionalAuthenticationFactorViewModel
            {
                RememberLogin = rememberLogin,
                ReturnUrl = returnUrl
            };

            return View(vm);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> AdditionalAuthenticationFactor(
            AdditionalAuthenticationFactorViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            // read identity from the temporary cookie
            var info = await HttpContext.AuthenticateAsync(Startup.TwoFactorAuthenticationScheme);
            var tempUser = info?.Principal;
            if (tempUser == null)
            {
                throw new Exception("2FA error");
            }

            // ... check code for user
            if (!await _twoFactorAuthenticationService.IsValidTemporaryCodeAsync(tempUser.GetSubjectId(), model.Code))
            {
                ModelState.AddModelError("code", "2FA code is invalid.");
                return View(model);
            }

            // login the user
            AuthenticationProperties props = null;
            if (AccountOptions.AllowRememberLogin && model.RememberLogin)
            {
                props = new AuthenticationProperties
                {
                    IsPersistent = true,
                    ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
                };
            }

            // issue authentication cookie for user
            var user = await _usersService.GetUserBySubjectIdAsync(tempUser.GetSubjectId());
            await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username));
            await HttpContext.SignInAsync(user.SubjectId, user.Username, props);

            // delete temporary cookie used for 2FA
            await HttpContext.SignOutAsync(Startup.TwoFactorAuthenticationScheme);

            if (_interaction.IsValidReturnUrl(model.ReturnUrl) || Url.IsLocalUrl(model.ReturnUrl))
            {
                return Redirect(model.ReturnUrl);
            }

            return Redirect("~/");
        }
توضیحات:
- فراخوانی HttpContext.SignInAsync با اسکیمای مشخص شده، یک کوکی رمزنگاری شده را در اکشن متد Login ایجاد می‌کند. اکنون در اینجا با استفاده از متد HttpContext.AuthenticateAsync و ذکر همان اسکیما، می‌توانیم به محتوای این کوکی رمزنگاری شده دسترسی داشته باشیم و از طریق آن، Id کاربر را استخراج کنیم.
- اکنون که این Id را داریم و همچنین Code موقتی نیز از طرف کاربر ارسال شده‌است، آن‌را به متد IsValidTemporaryCodeAsync که پیشتر در مورد آن توضیح دادیم، ارسال کرده و اعتبارسنجی می‌کنیم.
- در آخر این کوکی رمزنگاری شده را با فراخوانی متد HttpContext.SignOutAsync، حذف و سپس یک کوکی جدید را بر اساس اطلاعات هویت کاربر، توسط متد HttpContext.SignInAsync ایجاد و ثبت می‌کنیم تا کاربر بتواند بدون مشکل وارد سیستم شود.


View متناظر با آن نیز در فایل src\IDP\DNT.IDP\Views\Account\AdditionalAuthenticationFactor.cshtml، به صورت زیر تعریف شده‌است تا کد موقتی را به همراه آدرس بازگشت پس از ورود آن، به سمت سرور ارسال کند:
@model AdditionalAuthenticationFactorViewModel

<div>
    <div class="page-header">
        <h1>2-Factor Authentication</h1>
    </div>

    @Html.Partial("_ValidationSummary")

    <div class="row">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title">Input your 2FA code</h3>
            </div>
            <div class="panel-body">
                <form asp-route="Login">
                    <input type="hidden" asp-for="ReturnUrl" />
                    <input type="hidden" asp-for="RememberLogin" />

                    <fieldset>
                        <div class="form-group">
                            <label asp-for="Code"></label>
                            <input class="form-control" placeholder="Code" asp-for="Code" autofocus>
                        </div>

                        <div class="form-group">
                            <button class="btn btn-primary">Submit code</button>
                        </div>
                    </fieldset>
                </form>

            </div>
        </div>
    </div>
</div>

آزمایش برنامه جهت بررسی اعتبارسنجی دو مرحله‌ای

پس از طی این مراحل، اعتبارسنجی دو مرحله‌ای در برنامه فعال شده‌است. اکنون برای آزمایش آن، برنامه‌ها را اجرا می‌کنیم. پس از لاگین، صفحه‌ی زیر نمایش داده می‌شود:


همچنین کد موقتی این مرحله را نیز در لاگ‌های برنامه مشاهده می‌کنید:


پس از ورود آن، کار اعتبارسنجی نهایی آن انجام شده و سپس بلافاصله به برنامه‌ی MVC Client هدایت می‌شویم.


اضافه کردن اعتبارسنجی دو مرحله‌ای به قسمت ورود از طریق تامین کننده‌های هویت خارجی

دقیقا همین مراحل را نیز به اکشن متد Callback کنترلر ExternalController اضافه می‌کنیم. در این اکشن متد، تا قسمت کدهای مشخص شدن user آن که از اکانت خارجی وارد شده‌است، با قبل یکی است. پس از آن تمام کدهای لاگین شخص به برنامه را از اینجا حذف و به اکشن متد جدید AdditionalAuthenticationFactor در همین کنترلر منتقل می‌کنیم.




کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشه‌ی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشه‌ی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آن‌را اجرا کنید تا برنامه‌ی IDP راه اندازی شود.
- در آخر به پوشه‌ی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحه‌ی login نام کاربری را User 1 و کلمه‌ی عبور آن‌را password وارد کنید.
نظرات مطالب
سازماندهی برنامه‌های Angular توسط ماژول‌ها
توضیحات تکمیلی در مورد Core Module

اگر به CoreModule تعریف شده دقت کنید، یک چنین تعریفی در آن ذکر شده‌است:
   providers: [ UserRepositoryService ]
برای درک یک چنین تعریفی، نیاز است با نحوه‌ی عملکرد Angular Injector آشنا شد. برای این منظور فرض کنید برنامه‌ای را با سه ماژول طراحی کرده‌اید: یک App Module و دو ماژول معمولی و دیگری Lazy loaded. به ازای هر برنامه، Angular یک Root Injector را ایجاد می‌کند و کار آن تزریق سرویس‌ها، در مکان‌هایی است که به آن‌ها نیاز هست. این Root Injector نیز در App Module و در بالاترین سطح برنامه تشکیل می‌شود.
اکنون فرض کنید سرویسی را در ماژول معمولی غیر Lazy loaded تعریف کرده‌اید. Angular این سرویس را در Root Injector ثبت می‌کند. به این ترتیب این سرویس در کل برنامه قابل دسترسی می‌شود. اما اگر سرویسی را در یک ماژول Lazy loaded تعریف کنید، Angular کار ایجاد یک Injector جداگانه را برای آن ماژول و در داخل آن انجام می‌دهد. این Injector مجزا است از Root Injector قرار گرفته‌ی در App Module. سپس این سرویس در این Injector جدید ثبت می‌شود. به این ترتیب، وهله‌ی تهیه شده‌ی از این سرویس، تنها درون این ماژول Lazy loaded قابل دسترسی است. حتی اگر این دو سرویس ثبت شده‌ی در Root Injector و Injector مخصوص ماژول Lazy loaded از یک کلاس تهیه شوند، باز هم دو وهله‌ی مختلف از آن ارائه می‌شوند. برای مثال اگر UserRepositoryService را در ماژول معمولی و ماژول Lazy loaded مورد استفاده قرار دهیم، دو وهله‌ی مجزای از آن‌ها تشکیل خواهد شد که دیگر با هم همگام نبوده و کار ردیابی اطلاعات کاربر جاری سیستم را با مشکل مواجه می‌کنند.
ایجاد وهله‌ی دوم از یک سرویس، تنها منحصر است به ماژول‌های Lazy loaded. اما اگر این سرویس در ماژول‌های معمولی مختلفی مورد استفاده قرار گیرد، Angular تنها یک وهله از آن‌را ایجاد خواهد کرد. به همین جهت است که در این‌جا CoreModule را تعریف کردیم. این ماژول مکانی است که قرار است سرویس‌های اشتراکی در کل برنامه در آن قرار گیرند. چون این ماژول هیچگاه Lazy loaded نخواهد شد، هرگاه سرویسی را توسط آن ارائه دهیم، در Root Injector مربوط به App Module ثبت می‌شود. به این ترتیب تبدیل به یک Singleton Service قابل دسترسی در کل برنامه می‌شود.
باید دقت داشت که از لحاظ فنی، تفاوتی بین Core Module و یک ماژول معمولی غیر Lazy loaded نیست و بیشتر هدف از آن نظم بخشیدن به تعریف سرویس‌هایی است که قرار است در طول عمر برنامه و در تمام انواع ماژول‌های آن، توسط یک وهله قابل دسترسی شوند.
بنابراین تفاوتی نمی‌کند که یک سرویس را درون Core Module تعریف کنید و یا یک ماژول معمولی. این سرویس همواره در Root Injecror ثبت خواهد شد؛ مگر اینکه آن ماژول Lazy loaded باشد. در این حالت اگر سرویسی تنها قرار است در یک ماژول خاص استفاده شود، بهتر است آن‌را جهت مدیریت بهتر برنامه، درون همان پوشه تعریف کرد. هرچند از لحاظ فنی، این سرویس‌ها نهایتا در Root Injector مربوط به App Module ثبت و ارائه می‌شوند (از این لحاظ فرقی بین یک Core Module و Feature Modules نیست).
مطالب
انجام عملیات طولانی مدت با Web Workers
امروزه استفاده از صفحات وب، در همه امور به خوبی به چشم می‌خورد و تاثیر این فناوری را می‌توان در تمام عرصه‌های تولید و استفاده از نرم افزار دید. web worker یکی از فناوری‌های تحت وب بوده که توسط W3C ارائه شده است. وب ورکر به شما اجازه می‌دهد تا بتوانید عملیاتی را که نیاز به زمان زیادی برای پردازش دارد، در پشت صحنه انجام دهید؛ بدون اینکه وقفه‌ای در پردازش UI ایجاد شود. وب ورکر حتی به شما اجازه می‌دهد چند thread را همزمان اجرا کنید و پردازش‌هایی موازی یکدیگر داشته باشید. از آنجا که وب ورکرها یک ترد پردازشی جدا از UI به حساب می‌آیند، شما دسترسی به DOM ندارید؛ ولی می‌توانید از طریق ارسال پیام، با صفحه وب تعامل داشته باشد.

قبل از استفاده از وب ورکر، بهتر هست مرورگر را بررسی کنیم که آیا از این قابلیت پشتیبانی می‌کند یا خیر؟ روش بررسی کردن این قابلیت، شیوه‌های مختلفی دارد که به تعدادی از آن‌ها اشاره می‌کنیم:
typeof(Worker) !== "undefined"

 <script src="/js/modernizr-1.5.min.js"></script>
Modernizr.webworkers
هر کدام از عبارات بالا را اگر در شرطی بگذارید و جواب true بازگردانند به معنی پشتیبانی مرورگر این ویژگی است. modernizr فریمورکی جهت بررسی قابلیت‌های موجود در مرورگر است.
نحوه پشتیبانی وب ورکرها در مروگرهای مختلف به شرح زیر است:

برای ایجاد یک وب ورکر ابتدا لازم است تا کدهای پردازشی را داخل یک فایل js جداگانه بنویسیم. در این مثال ما قصد داریم که شمارنده‌ای را بنویسیم:
var i=0;

function timedCount() {
    i=i+1;
    postMessage(i);
    setTimeout("timedCount()", 500);
}

timedCount();

سپس در فایل HTML به شکل زیر وب ورکر را مورد استفاده قرار می‌دهیم. در سازنده Worker، ما آدرس فایل js را وارد می‌کنیم و برای توقف آن نیز از متد terminate استفاده می‌کنیم:
<!DOCTYPE html>
<html>
  <head>
    <script>
    var worker;

    function Start()
    {
      worker=new Worker("webwroker-even-numbers.js");
      worker.onmessage=(event)=>
      {
        document.getElementById("output").value=event.data;
      }
    }
    function Stop()
    {
      worker.terminate();
    }
    </script>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <input type="text" id="output"/>
    <button onclick="Start();">Start Worker</button>
<button onclick="Stop();">Stop Worker</button>
  </body>
</html>
در صورتی که خطایی در ورکر رخ بدهد، می‌توانید از طریق متد onerror آن را دریافت کنید:
      worker.onerror = function (event) {
            console.log(event.message, event);
         };
مقدار برگشتی event شامل اطلاعات زیادی در مورد خطاست که شامل نام و مسیر فایل خطا، شماره خط و شماره ستون خطا، پیام خطا و ... می‌شود.
همچنین برای ورکر هم می‌توانید پیامی را ارسال کنید، برای همین کد زیر را به کد ورکر اضافه می‌کنیم:
self.onmessage=(event)=>{
  i=event.data;
};
و در صفحه HTML هم کد دریافت پیام از ورکر را به شکل زیر تغییر میدهیم:
  worker.onmessage=(event)=>
      {
        document.getElementById("output").value=event.data;
        if(event.data==8)
        worker.postMessage(100);
      }
در این حالت اگر عدد به هشت برسد، ما به ورکر می‌گوییم که عدد را به صد تغییر بده.

روش‌های ارسال پیام
به این نوع ارسال پیام، Structure Cloning گویند و با استفاده از این شیوه، امکان ارسال نوع‌های مختلفی امکان پذیر شده است؛ مثل فایل‌ها، Blob‌ها، آرایه‌ها و کلاس‌ها و ... ولی باید دقت داشته باشید که این ارسال پیام‌ها به صورت کپی بوده و آدرسی ارجاع داده نمی‌شود و باید مدنظر داشته باشید که ارسال یک فایل، به فرض پنجاه مگابایتی، به خوبی قابل تشخیص است. طبق نظر گوگل، از حجم 32 مگابایت به بعد، این گفته به خوبی مشهود بوده و زمانبر می‌شود. به همین علت فناوری با نام Transferable Objects ایجاد شده است که "کپی صفر" Zero-Copy را پیاده سازی می‌کند و باعث بهبود عملگر کپی می‌شود:
worker.postMessage(arrayBuffer, [arrayBuffer]);
 پارامتر اول آن، آرایه بافر شده است و دومی هم لیست آیتم‌هایی است که قرار است انتقال یابند:
var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);
if (ab.byteLength) {
  alert('Transferables are not supported in your browser!');
} else {
  // Transferables are supported.
}
یا ارسال اطلاعات بیشتر:
worker.postMessage({data: ab1, moreData: ab2},
                   [ab1, ab2]);
در صورتیکه بتواند انتقال را انجام بدهد، byteLength حجم اطلاعات ارسالی را بر می‌گرداند؛ در غیر اینصورت عدد 0 را به عنوان خروجی بر می‌گرداند. در این پرسش و پاسخ می‌توانید نمونه یک انتقال و دریافت را در این روش، ببینید.
نمودار زیر مقایسه‌ای بین Structure Cloning و Transferable Objects است که توسط گوگل منتشر شده است:

RTT=Round Trip Time 

نمودار بالا برای یک فایل 32 مگابایتی است که زمان رفت به ورکر و پاسخ (برگشت از ورکر) را اندازه گرفته‌اند. در ستون‌های اول، این موضوع برای فایرفاکس به روش Structure Cloning  به مدت 302 میلی ثانیه زمان برد که همین موضوع برای Transferables حدود 6.6 میلی ثانیه زمان برد.

آقای اریک بایدلمن در بخش مهندسی کروم گوگل می‌گوید: همین سرعت به ما در انتقال تکسچرها و مش‌ها در WebGL کمک می‌کند.


استفاده از اسکریپت خارجی

در صورتیکه قصد دارید از یک اسکرپیت خارجی، در ورکر استفاده کنید، تابع importScripts برای اینکار ایجاد شده است:

importScripts('script1.js');
importScripts('script2.js');
که البته به طور خلاصه‌تری نیز می‌توان نوشت:
importScripts('script1.js', 'script2.js');

Inline Worker
اگر بخواهید در همان صفحه اصلی یک ورکر را ایجاد کنید و فایل جاوا اسکریپتی خارجی نداشته باشید، می‌توانید از inline worker استفاده کنید. در این روش باید یک نوع blob را ایجاد کنید:
var blob = new Blob([
    "onmessage = function(e) { postMessage('msg from worker'); }"]);

// یک آدرس همانند آدرس ارجاع به فایل درست میکند
var blobURL = window.URL.createObjectURL(blob);

var worker = new Worker(blobURL);
worker.onmessage = function(e) {
  // e.data...
};
worker.postMessage(); // ورکر آغاز می‌شود
کاری که متد حیرت انگیز createObjectURL انجام می‌دهد این است که از داده‌های ذخیره شده در یک blob یا نوع فایل، یک آدرس ارجاعی شبیه آدرس زیر را ایجاد می‌کند:
blob:http://localhost/c745ef73-ece9-46da-8f66-ebes574789b1
آدرس‌هایی که این متد تولید می‌کند، یکتا بوده و تا پایان عمر صفحه، اعتبار دارند. به همین دلیل هر موقع به آن‌ها نیاز نداشتید، از دست آن‌ها خلاص شوید، تا حافظه به هدر نرود. برای آزادسازی حافظه می‌توان دستور زیر را به کار برد:
window.URL.revokeObjectURL(blobURL);
مرورگر کروم با دستور زیر به شما اجازه می‌دهد همه آدرس‌های blob‌ها را ببینید:
chrome://blob-internals
نظرات اشتراک‌ها
معرفی کتابخانه‌ی DNTScanner.Core
ممنون از زحمات.در مورد این کتابخونه امکانش هست راهنمایی بفرمایید.
اگر اشتباه نوشتم اصلاح بفرمایید.
اگر بخوام این کتابخونه رو در پروژه ای استفاده کنم به نظرم باید علاوه بر ناگت ، خروجی پروژه ویندوز سرویس رو هم به طریقی از سمت قسمت وب اجرا کنم . و سیستمی که اسکنر در اون نصب شده باید برنامه ویندوز سرویس رو اجرا کنه . پس در واقع اگر کلاینت دارای اسکنر بود و سرور دارای پروژه من ، به طریقی باید ارتباط signalr رو بین این دو بخش برقرار کنم ؟
اشتراک‌ها
پشتیبانی همزمان از REST و gRPC در ASP.NET Core API
اکثر برنامه نویسان، به احتمال زیاد تاکنون API‌‌های خود را در قالب REST به مشتریان عرضه کرده اند و هم توسعه دهنده و هم خدمات گیرنده از سادگی استفاده از REST API راضی بوده اند. اما در نقطه ای از فرآیند توسعه خود، به جایی می‌رسیم که علاوه بر سادگی توسعه، Performance نقش پررنگی پیدا می‌کند بخصوص با افزایش تعداد Method‌های API و افزایش تعداد مشتریان، آنجاست که CPU سروری که API ما را هاست می‌کند، گاهی به سقف می‌چسبد، ترافیک شبکه به شدت افزایش می‌یابد و در پاسخ به اینکه آیا شما Go Client یا Python Client هم برای API خود دارید، پاسخی جز "خیر" نداریم. اینجاست که جستجو میکنیم و میبینیم راه‌های زیادی پیش روی ماست، یکی از این راه ها، که برای هر سه نیاز فوق پاسخ مناسبی می‌دهد، gRPC هست. نمونه ای عملیاتی از API ای که REST و gRPC را همزمان با هم پشتیبانی کند، در این مخزن گیت هاب  قرار داده ام که می‌توانید برای مقایسه Performance و حجم اطلاعات تبادل شده با هر دو روش از آن استفاده کنید. در این نمونه کد، با فراخوانی یک سرویس ساده، نتایج زیر به دست آمد:
  • سرعت اجرا (سرویس گیرنده): gRPC تقریبا 5 برابر سریعتر از REST پیام‌ها را دریافت کرد.
  • حجم دیتای انتقالی (سرویس گیرنده): حجم پیام‌های gRPC تقریبا نصف REST بود.
پشتیبانی همزمان از REST و gRPC در ASP.NET Core API
مطالب
ایجاد Self-Signed Certificate در IIS Express
در حال نوشتن یک برنامه‌ی ویندوزی بودم که نیاز به یک وب سرویس داشت و اتصال باید از طریق HTTPS انجام می‌گرفت. پروژه‌ی وب سرویس را تنظیم کردم تا SSL را هم پشتیبانی کند (تنظیمات انجام شد). وقتی می‌خواستم روی یک سیستم دیگر، پروژه را در ویژوال استودیو باز و اجرا کنم، با پیام خطای «عدم وجود ارتباط امن» در هنگام برقراری ارتباط با وب سرویس مواجه شدم.
که بعد از بررسی به راه حل‌های زیر رسیدم:

راه حل اول

بعد از اجرای وب سرویس و باز کردن آدرس آن به صورت HTTPS در مرورگر، پیام مبنی بر عدم اعتبار گواهی HTTPS را در آدرس وارد شده، مشاهده می‌کنیم. (Untrusted certificate) (که نسبت به مرورگر استفاده شده، این پیام متفاوت است و من در اینجا از مرورگر IE استفاده می‌کنم)

  1. بر روی Certificate error در نوار آدرس، کلیک کرده و View certificates را انتخاب می‌کنیم.
  2. وقتی پنجره Certificate باز شد بر روی دکمه Install Certificate کلیک کرده و پنجره Certificate Import Wizard باز شده و Next را می‌زنیم و Place all certificates in the following store را انتخاب می‌کنیم و بر روی دکمه Browse کلیک می‌کنیم.
  3. از پنجره باز شده Trusted Root Certification Authorities را انتخاب می‌کنیم و بر روی دکمه OK، کلیک می‌کنیم.
  4. سپس Next را می‌زنیم و در پایان بر روی دکمه Finish کلیک می‌کنیم.
  5. پس از اتمام Wizard، پنجره Security Warning به شما نمایش داده می‌شود که باید بر روی Yes آن کلیک کنید، بعد از تایید، پیام .The import was successful به شما نمایش داده می‌شود.


راه حل دوم

ممکن است کامپیوتر شما با توسعه دهندگان دیگر که با حساب کاربری خود وارد می‌شوند، مشترک باشد و بخواهید اطلاعات مربوط به گواهی اعتبار، به صورت مشترک استفاده شود. جزئیات در این روش بیشتر از روش قبل است.

  1. بازکردن پنجره Run و وارد کردن دستور mmc و زدن دکمه OK.
  2. اضافه کردن Snap-in
    • انتخاب Add/Remove Snap-in  از منوی File
    • انتخاب Certificates  از لیست سمت چپ و انتخاب دکمه Add
    • در پنجره Certificates Snap-ins انتخاب گزینه Computer account و انتخاب دکمه Next
    • انتخاب Local computer  و کلیک بر روی دکمه Finish
    • انتخاب دکمه OK
  3. استخراج IIS Express certificate از computer’s personal store
    • در قسمت Console Root ، بخش Certificates (Local Computer)، سپس قسمت Personal و انتخاب Certificates.
    • انتخاب گواهی با مشخصات زیر:
      • "Issued to = "localhost
      • "Issued by = "localhost
      • "Friendly Name = "IIS Express Development Certificate
    • انتخاب گزینه Export از زیرمنوی All Tasks در منوی Action
    • پنجره Certificate Export Wizard باز شده و انتخاب دکمه Next
    • انتخاب No, do not export the private key و انتخاب دکمه Next
    • انتخاب DER encoded binary X.509 (.CER) و انتخاب دکمه Next
    • انتخاب مسیر ذخیره فایل گواهی تصدیق مجوز و انتخاب دکمه Next
    • انتخاب دکمه Finish برای انجام عملیات Export و مشاهده پیام موفقیت
  4. وارد کردن IIS Express certificate به computer’s Trusted Root Certification Authorities store
    1. در قسمت Console Root ، بخش Certificates (Local Computer)، سپس قسمت Trusted Root Certification Authorities و انتخاب Certificates.
    2. انتخاب گزینه Import از زیرمنوی All Tasks در منوی Action
    3. پنجره Certificate Export Wizard باز شده و انتخاب دکمه Next
    4. انتخاب مسیر فایل ذخیره شده در مرحله قبل و انتخاب دکمه Next
    5. انتخاب Place all certificates in the following store و در قسمت Certificate store ، انتخاب بخش Trusted Root Certification Authorities و انتخاب دکمه Next
    6. انتخاب دکمه Finish برای انجام عملیات Import و مشاهده پیام موفقیت و مشاهده گواهی تصدیق مجوز با نام localhost در لیست Trusted Root Certification Authorities


راه حل سوم

با استفاده از Developer Command Prompt نیز می‌توان این کار را انجام داد.

  1. با اجرای دستور زیر و دریافت فایل خروجی
    makecert -r -n "CN=localhost" -b 01/01/2000 -e 01/01/2099 -eku 1.3.6.1.5.5.7.3.1 -sv localhost.pvk localhost.cer
    
    cert2spc localhost.cer localhost.spc
    
    pvk2pfx -pvk localhost.pvk -spc localhost.spc -pfx localhost.pfx
  2. اجرای فایل localhost.pfx و وقتی پنجره Certificate Import Wizard باز شد، Next را می‌زنیم.
  3. نام فایل انتخاب شده را در این قسمت مشاهده می‌کنیم و Next را می‌زنیم.
  4. در صورت داشتن کلمه عبور، آن را وارد کرده (که در اینجا کلمه عبوری را تعریف نکرده‌ایم) و Next را می‌زنیم.
  5. صفحه Place all certificates in the following store را انتخاب می‌کنیم و بر روی دکمه Browse کلیک می‌کنیم.
  6. از پنجره باز شده، Trusted Root Certification Authorities را انتخاب می‌کنیم و بر روی دکمه OK، کلیک می‌کنیم.
  7. سپس Next را می‌زنیم و در پایان بر روی دکمه Finish کلیک می‌کنیم.
  8. پس از اتمام Wizard، پنجره Security Warning به شما نمایش داده می‌شود که باید بر روی Yes کلیک کنید. بعد از تایید، پیام .The import was successful به شما نمایش داده می‌شود.

نکته: در صورتی که بخواهید برنامه شما (windows form) بتواند به سرور از طریق HTTPS اتصال پیدا کند، باید این فایل pfx بر روی هر کلاینت نصب شده باشد. شما می‌توانید با اجرای دستور زیر در ابتدای فایل program.cs این کار را انجام دهید.

var cert = new X509Certificate2( Properties.Resources.localhost );

var store = new X509Store( StoreName.AuthRoot, StoreLocation.LocalMachine );
store.Open(OpenFlags.ReadWrite);
store.Add(cert);
store.Close();
در اینجا من فایل localhost را به Resource برنامه اضافه کردم.
نظرات مطالب
EF Code First #1
با سلام
ممنون از مطالب مفید شما.
من کلا با اصل code first مشکل دارم.
صرف وقت کم درسته برنامه سریع تر بالا می یاد ولی از طرف دیگه باید وقت بیشتری صرف توسعه مواردی کرد که توسط نویسنده فریم ورک پیش بینی نشده.
مشکل دومی که وجود داره در code first خیلی از ویژگی هایی که در هر دیتابیس وجو داره از دست داده می شه مثل استفاده از sp
در آخر یک سوال آیا شما حاضری یک برنامه پیچیده تحت وب با این مدل پیاده سازی کنی به عنوان یک مهندس صاحب نظر؟
مطالب
آشنایی با SharePoint Sandboxed Solutions
از Sandbox برای ساخت و انتشار وب پارت هایی استفاده می‌شود که نیاز به مداخله مدیریتی (administrative intervention) وجود ندارد. به این معنی که شما می‌توانید وب پارت خود را در محل شیرپوینت و هاست آن یا با استفاده از زیر ساخت‌های Cloud و دور از هاست آن ، بارگذاری کرده و انتشار دهید.
هر Solution که برای Sandbox ایجاد می‌شود ، در یک گالری خاص به نام Solution Gallery در Site Collection مربوطه ایجاد می‌شود و تمام این solution‌ها می‌توانند قعال یا غیر فعال شوند .


برای استفاده از این مزایا باید سرویس Microsoft SharePoint Foundation User Code Service فعال باشد .

 



استفاده از Sandbox Solution نیازمند 3 چیز است :

1 - User Code Service یا SPUCHostService.exe : این سرویس وظیفه مدیریت کدهای Sandbox روی سرور و میزبانی درخواست‌های Sandbox به سرور را دارد.

2 - Sandbox Worker Process یا SPUCWorkerProcess.exe : این سرویس برای مدیریت پردازش کدهای اجرایی Sandbox استفاده می‌شود خصوصا در مورارد امنیتی (فرض کنید درون کد حلقه بی نهایتی وجود داشته باشد. اگر بار اجرایی آن بر روی w3wp.exe باشد ، باعث متوقف شدن کارکرد تمام پروسه می‌شود. بار این مسئولیت بر دوش SPUCWorkerProcess.exe می‌باشد ) 

3 - Sandbox Worker Process Proxy یا SPUCWorkerProcessProxy.exe : این سرویس برای اجرای کدهای Client Object Model در sandbox استفاده می‌شود. 


می توانید ارتباط پردازه‌های فوق را در نمودار زیر مشاهد کنید :



با توجه به این توضیحات به دلیل استفاده از Sandbox می‌پردازیم :

1 - sandbox Solution‌ها امن هستند. 

2 - sandbox Solution‌ها می‌توانند مانیتور شوند. 

3 - sandbox Solution‌ها حداقل تاثیر را روی هم دارند. 

4 - sandbox Solution‌ها عملیات I/O کمی روی فایل‌های سیستمی دارند 

5 - sandbox Solution‌ها امکان debugging دارند 

6 - sandbox Solution‌ها امکان توسعه ، انتشار و به روز رسانی دارند 

7 - sandbox Solution‌ها امکان تعریف Policy برای CAS ایجاد می‌کند (Code Access Security برای قابل اطمینان ساخت کد ها) 

8 - sandbox Solution‌ها امکان اعتبار سنجی به کمک کلاس SPSolutionValidator را می‌دهد. 

9 - sandbox Solution‌ها امکان تعریف SLA های مختلف را می‌دهد. 



چه قسمت هایی توسط Sandbox Solution پشتیبانی می‌شود :

 

هنگام ایجاد یک پروژه از نوع sandbox در ویژوال استودیو ، یکسری امکانات از برنامه نویس سلب می‌شود و محدودیت‌های برای استفاده از کلاس‌ها برای وی اعمال می‌شود .موارد زیر ، از جمله مواردی هستند که توسعه دهنده می‌تواند از آنها در sandbox استفاده کند :

 اسفاده محدود از لیست‌ها ، کار با Web Template‌ها و ویژگی‌های آنها ، Content Type‌ها و فیلد‌ها ، ماژول‌ها و فایل‌ها ، وب پارت‌های مشتق شده از کلاس WebPart ، برخی Event Receiver‌ها ، بعضی از Custom Action و چرخه‌های کاری . و از مواردی که نمی‌توان در sandbox استفاده کرد می‌توان به ویژگی‌های موجود در Web Application ، ویژگی‌های موجود در Farm و Timer job ، استفاده از عملیات I/O و استفاده از ADO و برخی کلاس‌های دیگر در دات نت اشاره کرد .
مدیران سایت می‌توانند Solution‌ها را مونیتور کنند و برای Resource Point هایی که در یک روز مصرف می‌کنند Quota تعریف کنند .پیش فرض این مقدار 300 Resource Point در روز است .
 هر solution در هنگام deploy شدن جهت معتبر بودن چک می‌شود و سپس منتشر شود. در صورتی که Valid نباشد ، خطایی به برنامه نویس نشان می‌دهد.
 در انتها اشاره ای هم به استفاده از STSADM یا Powershell برای deploy کردن solution می‌کنم که توسط دستورات زیر قابل اجراست :
 
Add-SPSolution c:\code\SharePointProject2\bin\debug\SharePointProject2.wsp
stsadm –o addsolution –name SharePointProject2.wsp
همانطور که پسوند فایل‌های solution را مشاهده می‌کند wsp هستند که اگر آنها را به cab تغییر نام دهید میتوانید محتویات آنها را مشاهده کنید
 
نظرات مطالب
4# آموزش سیستم مدیریت کد Git : نصب و پیکر‌‏بندی

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

سوالی که بنده داشتم این بود که چرا و به چه علتی با وجود ابزاری مثل visual studio team foundation server 2010 , 2012  باید با ابزاری مثل git کار کرد و البته با توجه به اینکه دوستان این سایت یا وبلاگ عموما در حوضه دات نت هستند این سوال مهم‌تر هم میشه ، در مورد مطالب در خصوص git  باید بگم طرز کار کردن با این ابزار بسیار پیچیده‌تر و غیر اصولی‌تر از tfs هست ، مثلا اینکه خود فایل رو پس از تغییر نگهداری میکنه یک نقطه ضعفه ولی نویسنده مطلب از اون به عنوان نقطه قوت یاد کرده ، اگر فایل به صورت مجموعه تغییرات ذخیره بشه هم حجم اطلاعات ذخیره شده کاهش پیدا میکنه و هم منبع نگهداری سورس‌ها میتونه مثل ماشین زمان ما رو به جلو و عقب ببره و محدودیتی نخواهد داشت ، در هر حال با توجه به محصول مایکروسافت بودن tfs  و رایگان بودن git فکر کنم حتی مقایسه این دو حتی درست هم نباشه.

با تشکر از تمامی زحمات شما دوستان عزیز