مطالب
شیوه‌نامه‌های مقدماتی بوت استرپ 4
وقتی صفحه‌ی وبی را باز می‌کنید، تنظیمات بسیاری بر روی ظاهر آن تاثیرگذار هستند. برای مثال خود مرورگر دارای تنظیماتی است که بر روی ظاهر پیش‌فرض عناوین و عناصر مختلف قرار گرفته شده‌ی بر روی صفحه تاثیر گذار است. به این موارد Browser Styles گفته می‌شود که با Custom Styles ما قابلیت بازنویسی را دارند. در این بین، شیوه‌نامه‌های بوت استرپ، بین Browser Styles و شیوه‌نامه‌های سفارشی ما قرار می‌گیرند تا ظاهر بهتری را برای عناصر مختلف صفحه ارائه دهند.


تایپوگرافی مقدماتی بوت استرپ 4

شیوه‌نامه‌های همراه با بوت استرپ، رفتار و تنظیمات پیش‌فرض مرورگر را بازنویسی می‌کنند. این بازنویسی با فایل node_modules\bootstrap\scss\_reboot.scss شروع می‌شود. اگر مجموعه‌ی بوت استرپ را توسط روش معرفی شده‌ی در مطلب «روش‌های مختلف دریافت و نصب بوت استرپ 4» دریافت کرده باشید، کدهای کامل SASS آن، در پوشه‌ی scss این مجموعه، موجود هستند که یکی از آن‌ها فایل reboot است. کار آن نرمال سازی شیوه‌نامه‌ها، به نحوی است که در مرورگرها مختلف و همچنین وسایل نمایشی متفاوت، یکسان به نظر برسند:
 - برای مثال در این فایل از روش اندازه گیری rem استفاده شده‌است تا مدیریت اندازه‌های آن در سکوهای کاری مختلف قابل کنترل شود.
 - در اینجا از margin-top به طور کامل صرفنظر شده‌است، تا بتوان اندازه‌گیری فواصل بین عناصر را بهتر محاسبه کرد. بوت استرپ 4 تنها یک margin را در پایین تمام عناصر صفحه، تنظیم می‌کند. بنابراین آگاهی از وجود این پیش‌فرض، تنظیم فواصل بین عناصر را نیز ساده‌تر می‌کند.
 - در این فایل در همه‌جا از خاصیت inherit استفاده شده‌است تا امکان بازنویسی شیوه‌نامه‌های آن توسط custom styles ما ساده‌تر شود.
 - پیش‌فرض دیگری که در این نگارش از بوت استرپ تنظیم شده‌است، border-box می‌باشد. به این ترتیب اندازه گیری عرض عناصر ساده‌تر می‌شوند. برای مثال اگر عرض یک div را مساوی 200px قرار دهید، یک padding پیش‌فرض نیز برای آن درنظر گرفته شده‌است و padding سفارشی تنظیم شده‌ی برای آن بی‌اثر خواهد بود.
 - در این نگارش، فونت پیش‌فرض صفحه، به فونت پیش‌فرض سیستم تنظیم شده‌است و نه فونت از پیش تعیین شده‌ی خاصی. از این جهت که این قلم‌های سیستمی، دارای ویژگی‌های خاصی هستند که آن‌ها را برای سکوهای کاری مختلف، منحصربفرد می‌کنند.

مثال: نمایش تاثیر بوت استرپ 4 بر روی تایپوگرافی پیش‌فرض مرورگر
<body>
    <div class="container">
        <section class="content" id="mission">
            <h1>Our Commitment <small>to you</small></h1>
            <p>Wisdom Pet Medicine strives to blend the best in traditional and
                <em>alternative medicine</em> in the <strong>diagnosis and
                    treatment</strong> of companion animals including dogs,
                cats, birds, reptiles, rodents, and fish. We apply the wisdom
                garnered in the <mark>centuries old tradition</mark> of
                veterinary medicine, to find the safest treatments
                and&nbsp;cures.</p>
            <p>We strive to be your pet's medical <del>staff</del> experts from
                youth through the senior years. <small>We build preventative
                    health care plans for each and every one of our patients,
                    based on breed, age, and sex, so that your pet receives the
                    most appropriate care at crucial milestones.</small> We
                want to give your pet a long and healthy&nbsp;life.</p>
        </section>

        <section class="content" id="services">
            <h2>Exotic Pets</h2>
            <p>We offer <strong>specialized</strong> care for <em>reptiles,
                    rodents, birds,</em> and other exotic pets.</p>

            <h3>Grooming</h3>
            <p>Our therapeutic <span>grooming</span> treatments help battle
                fleas, allergic dermatitis, and other challenging skin
                conditions.</p>

            <h4>General Health</h4>
            <p>Wellness and senior exams, ultrasound, x-ray, and dental
                cleanings are just a few of our general health services.</p>

            <h5>Nutrition</h5>
            <p>Let our nutrition experts review your pet's diet and prescribe a
                custom nutrition plan for optimum health and disease
                prevention.</p>

            <h6>Pest Control</h6>
            <p>We offer the latest advances in safe and effective prevention
                and treatment of fleas, ticks, worms, heart worm, and other
                parasites.</p>

            <h2>Vaccinations</h2>
            <p>Our veterinarians are experienced in modern vaccination
                protocols that prevent many of the deadliest diseases in pets.</p>
        </section>
    </div>
</body>
با این خروجی:

با اعمال بوت استرپ


بدون اعمال بوت استرپ


در اینجا دو تصویر راملاحظه می‌کنید؛ یکی با اعمال bootstrap.min.css به صفحه‌است و دیگری با حذف آن از صفحه. به این ترتیب مشاهده می‌کنید که صرفا اعمال بوت استرپ به یک صفحه‌ی متداول، کیفیت نمایش آن‌را با بازنویسی شیوه‌نامه‌ی پیش‌فرض مرورگر، به نحو قابل ملاحظه‌ای بهبود بخشیده‌است و آن‌را زیباتر کرده‌است.
در اینجا تنها المان بوت استرپی که به صفحه اضافه شده‌است و جزو استانداردهای HTML نیست، یک div با کلاس container است:
<body>
    <div class="container">
کل محتوای صفحه جهت اعمال شیوه‌نامه‌های بوت استرپ، داخل این div قرار می‌گیرند. اولین تاثیر آن واکنشگرا کردن صفحه‌است و همچنین یک padding را نیز به قسمت‌های چپ و راست صفحه اضافه کرده‌است.
در این مثال تاثیر بوت استرپ را بر روی شیوه‌نامه‌های پیش‌فرض خصوصا  h1 تا h6، مشاهده می‌کنید.
روش دیگر تعریف headings در اینجا، استفاده از کلاس‌هایی با نام‌های مشابه است:
<div class="h1">Test div class H1</div>
علاوه بر آن، کلاس display نیز در اینجا برای تعیین اندازه‌ی headings سفارشی پیش بینی شده‌است که می‌توان از عدد 1 تا 4 را توسط آن تنظیم کرد:
<div class="display-1">Test div class display-1</div>
با این خروجی و اندازه در مقایسه با headings استاندارد که امکان تعریف تیترهایی بزرگ‌تر از اندازه‌های متداول را میسر می‌کنند:


همچنین اگر نیاز به بزرگتر نمایش دادن متن قسمت ابتدایی صفحه وجود داشت، می‌توان از کلاس Lead استفاده کرد:
<p class="lead">Testing a lead class</p>



کلاس‌های کمکی کار با متون در بوت استرپ 4

بوت استرپ 4 به همراه تعدادی کلاس کمکی کار با متون است که نیازهای متداول تایپوگرافی را برآورده می‌کنند:

1) کلاس‌های کمکی محل قرارگیری متون
- کلاس text-justify کار کشیدن و متناسب کردن یک پاراگراف را با گوشه‌های سمت چپ و راست صفحه انجام می‌دهد.
- کلاس text-nowrap از شکسته شدن متن به چندین سطر جلوگیری می‌کند. برای مثال می‌تواند برای نمایش کدها مناسب باشد.
- کلاس متغیر text-xx-pos برای تعیین محل قرارگیری متن کاربرد دارد:
در اینجا ذکر xx اختیاری است و می‌تواند sm، برای اندازه‌های صفحه‌ی بیشتر از 576px و یا md، برای اندازه‌های صفحه‌ی بیشتر از 768px و یا lg، برای اندازه‌های صفحه‌ی بیشتر از 992px و یا xl، برای اندازه‌های صفحه‌ی بیشتر از 1200px باشد. این اندازه‌های یاد شده را در ادامه بیشتر مشاهده خواهید کرد.
همچنین pos می‌تواند left ،center و یا right باشد.
برای مثال کلاس text-sm-center به این معنا است که متن مدنظر در break-point ایی به نام sm، یعنی با اندازه‌ی صفحه‌ی بیشتر از 576px، در وسط صفحه نمایش داده خواهد شد.

2) کلاس‌های نمایش upper-case و lower-case حروف
- کلاس text-lowercase کار نمایش lower-case کل یک پاراگراف اعمالی را انجام می‌دهد.
- کلاس text-uppercase کار نمایش upper-case کل یک پاراگراف اعمالی را انجام می‌دهد.
- کلاس text-capitalize، اولین حرف هر واژه را به صورت بزرگ نمایش می‌دهد.

3) کلاس‌های شیوه‌ی نمایش متون
کلاس font-weight-bold، کلاس font-weight-normal و کلاس font-italic نمایش ضخیم، عادی و یا italic متن را سبب می‌شوند.

مثال: بررسی تاثیر کلاس‌های کمکی کار با متون در بوت استرپ 4
<body>
    <div class="container">
        <section class="content" id="mission">
            <h1 class="text-center text-sm-right text-md-left text-uppercase">Our
                Commitment</h1>
            <p class="lead text-justify">Wisdom Pet Medicine strives to blend
                the best in
                traditional and <em>alternative medicine</em> in the <strong>diagnosis
                    and treatment</strong> of companion animals including dogs,
                cats, birds, reptiles, rodents, and fish. We apply the wisdom
                garnered in the <mark>centuries old tradition</mark> of
                veterinary medicine, to find the safest treatments
                and&nbsp;cures.</p>
            <p class="text-nowrap text-capitalize">We strive to be your pet's
                medical <del>staff</del>
                experts from
                youth through the senior years. <small>We build preventative
                    health care plans for each and every one of our patients,
                    based on breed, age, and sex, so that your pet receives the
                    most appropriate care at crucial milestones.</small> We
                want to give your pet a long and healthy&nbsp;life.</p>
        </section>

        <section class="content" id="services">
            <div class="display-4">Exotic Pets</div>
            <p>We <span class="font-weight-bold">offer</span> <strong class="font-weight-normal">specialized</strong>
                care for <em>reptiles,
                    rodents, birds,</em> and other exotic pets.</p>

            <h3 class="text-left text-md-center text-sm-right">Grooming</h3>
            <p>Our therapeutic <span>grooming</span> treatments help battle
                fleas, allergic dermatitis, and other challenging skin
                conditions.</p>

            <h4>General Health</h4>
            <p>Wellness and senior exams, ultrasound, x-ray, and dental
                cleanings are just a few of our general health services.</p>

            <h5>Nutrition</h5>
            <p>Let our nutrition experts review your pet's diet and prescribe a
                custom nutrition plan for optimum health and disease
                prevention.</p>

            <h6>Pest Control</h6>
            <p>We offer the latest advances in safe and effective prevention
                and treatment of fleas, ticks, worms, heart worm, and other
                parasites.</p>

            <h2>Vaccinations</h2>
            <p>Our veterinarians are experienced in modern vaccination
                protocols that prevent many of the deadliest diseases in pets.</p>
        </section>
    </div>
</body>
با این خروجی


در اینجا اگر کلاس text-right را به heading اضافه کنیم:
<h1 class="text-right">Our Commitment <small>to you</small></h1>
چنین خروجی حاصل می‌شود:


و یا می‌توان این کلاس‌ها را با هم ترکیب کرد:
<h1 class="text-center text-sm-right">Our Commitment <small>to you</small></h1>
این ترکیب به این معنا است:
- متن h1 در حالت عادی در وسط صفحه نمایش داده شود.


- اما متن مدنظر در break-point ایی به نام sm، یعنی با اندازه‌ی صفحه‌ی بیشتر از 576px، در سمت راست صفحه نمایش داده خواهد شد.


این اعداد توسط افزونه‌ی ViewPort نمایش داده شده‌اند.


همچنین تاثیر text-justify را نیز به اولین پاراگراف
<p class="lead text-justify">
به صورت ذیل مشاهده می‌کنید که در مقایسه با تصویر قبلی، سبب کشیده شدن متن و تنظیم آن با سمت راست و چپ صفحه شده‌است:


و یا اگر text-nowrap را به پاراگرافی اعمال کنیم:
<p class="text-nowrap">


سبب نمایش یک سطری آن خواهد شد که در اینجا با پدید آمدن یک اسکرول بار افقی، قابل ملاحظه‌است.


کلاس‌های کمکی کار با لیست‌ها و نقل قول‌ها در بوت استرپ 4

بوت استرپ 4 به همراه کلاس‌هایی کمکی برای کار با لیست‌ها و نقل قول‌ها است؛ مانند:
- کلاس list-unstyled سبب حذف bullets از یک لیست می‌شود.
- برای ایجاد لیست‌های Inline می‌توان از کلاس list-inline بر روی المان UL و سپس list-inline-item بر روی هر LI آن، کمک گرفت.
<body>
    <div class="container">
        <section class="content" id="services">
            <h2>Exotic Pets</h2>
            <p>We offer <strong>specialized</strong> care for <em>reptiles,
                    rodents, birds,</em> and other exotic pets.</p>
            <ul class="list-unstyled">
                <li>Grooming</li>
                <li>General Health</li>
                <li>Nutrition</li>
                <li>Pest Control</li>
                <li>Vaccinations</li>
            </ul>

            <ul class="list-inline">
                <li class="list-inline-item">Grooming</li>
                <li class="list-inline-item">General Health</li>
                <li class="list-inline-item">Nutrition</li>
                <li class="list-inline-item">Pest Control</li>
                <li class="list-inline-item">Vaccinations</li>
            </ul>
        </section>
    </div>
</body>
در این مثال نحوه‌ی حذف bullets و همچنین inline تعریف کردن دو لیست را مشاهده می‌کنید؛ با این خروجی:


در حالت لیست inline، آیتم‌های لیست از چپ به راست در یک سطر نمایش داده می‌شوند. برای مثال می‌تواند برای نمایش breadcrumbs در یک سایت مناسب باشد.
همچنین برای نمایش نقل قول‌ها می‌توان از کلاس blockquote و برای نمایش بهتر امضای آن از کلاس blockquote-footer استفاده کرد:
<body>
    <div class="container">
        <section class="content" id="testimonials">
            <h2>Testimonials</h2>

            <blockquote>
                During the summer, our rabbit, Tonto, began to have severe
                redness and itching on his belly and feet. I'm very thankful to
                the veterinarians and staff at Wisdom for the excellent care
                Tonto received, and for nipping his allergies in the bud, so to
                speak.
                Jane
            </blockquote>

            <blockquote class="blockquote text-right">
                When Samantha, our Siamese cat, began sleeping all the time and
                urinating excessively, we brought her to see the specialists at
                Wisdom. Now, two years later, Samantha is still free from any
                complications of diabetes, and her blood sugar regularly tests
                normal.
                <div class="blockquote-footer">
                    The McPhersons
                </div>
            </blockquote>
        </section>
    </div>
</body>


در اینجا دو blockquote را مشاهده می‌کنید. مورد اول بدون کلاس blockquote است و دومی به همراه این کلاس و یک footer تعریف شده‌است. همچنین می‌توان کلاس‌هایی مانند text-right را نیز به blockquote اضافه کرد.
البته در نگارش 4، حاشیه‌ی خاکستری blockquote که در نگارش سوم آن وجود داشت، حذف شده‌است.


کار با رنگ‌ها در بوت استرپ 4

بوت استرپ، به همراه تعدادی کلاس مخصوص رنگ‌ها است که از آن در همه جا استفاده می‌کند؛ مانند رنگ‌های دکمه‌ها، پس زمینه‌ها و متون.
1) کلاس‌های تعیین رنگ متون:


برای مثال در اینجا بجای Color می‌توان یکی از ثوابت ذیل آن‌را قید کرد؛ مانند text-primary و یا text-danger
این کلاس‌ها برای تعیین رنگ متون و همچنین لینک‌ها کاربرد دارند.


2) کلاس‌های تعیین رنگ پس زمینه:


در اینجا برای نمونه بجای Color می‌توان یکی از ثوابت ذیل آن‌را قید کرد؛ مانند bg-primary و یا bg-danger

مثال: اعمال رنگ‌های زمینه‌ای بوت استرپ
<body>
    <div class="container">
        <section class="content" id="services">
            <h2 class="text-danger">Our Mission</h2>
            <p class="bg-danger text-white">Wisdom Pet Medicine strives to
                blend the best in
                traditional and
                alternative medicine in the diagnosis and treatment of
                companion animals including dogs, cats, birds, reptiles,
                rodents, and fish. We apply the wisdom garnered in the
                centuries old tradition of veterinary medicine, to find the
                safest treatments and cures.</p>

            <ul>
                <li><a class="text-warning" href="#">Grooming</a></li>
                <li><a href="#">General Health</a></li>
                <li><a href="#">Nutrition</a></li>
                <li><a href="#">Pest Control</a></li>
                <li><a href="#">Vaccinations</a></li>
            </ul>

        </section>

        <section class="content" id="testimonials">
            <h2>Testimonials</h2>

            <blockquote class="blockquote bg-faded text-info">
                During the summer, our rabbit, Tonto, began to have severe
                redness and itching on his belly and feet. I'm very thankful to
                the veterinarians and staff at Wisdom for the excellent care
                Tonto received, and for nipping his allergies in the bud, so to
                speak.
                <div class="blockquote-footer">
                    Jane
                </div>
            </blockquote>
        </section>
    </div>
</body>
با این خروجی


در اینجا مثال‌هایی را از اعمال کلاس‌های رنگ‌های بوت استرپ مشاهده می‌کنید. همچنین امکان ترکیب آن‌ها مانند مثال زیر نیز وجود دارد:
<p class="bg-danger text-white">



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: Bootstrap4_02.zip
اشتراک‌ها
سورس نرم افزار چند رسانه ای قدرتمند Kodi با قابلیت های متعدد

 Kodi is an award-winning free and open source home theater/media center software and entertainment hub for digital media. With its beautiful interface and powerful skinning engine, it's available for Android, BSD, Linux, macOS, iOS and Windows. https://kodi.tv/  

سورس نرم افزار چند رسانه ای قدرتمند Kodi با قابلیت های متعدد
مطالب
راهنمای تغییر بخش احراز هویت و اعتبارسنجی کاربران سیستم مدیریت محتوای IRIS به ASP.NET Identity – بخش سوم
تغییر الگوریتم پیش فرض هش کردن کلمه‌های عبور ASP.NET Identity

کلمه‌های عبور کاربران فعلی سیستم با الگوریتمی متفاوت از الگوریتم مورد استفاده Identity هش شده‌اند. برای اینکه کاربرانی که قبلا ثبت نام کرده بودند بتوانند با کلمه‌های عبور خود وارد سایت شوند، باید الگوریتم هش کردن Identity را با الگوریتم فعلی مورد استفاده Iris جایگزین کرد.

برای تغییر روش هش کردن کلمات عبور در Identity باید اینترفیس IPasswordHasher را پیاده سازی کنید:
    public class IrisPasswordHasher : IPasswordHasher
    {
        public string HashPassword(string password)
        {
            return Utilities.Security.Encryption.EncryptingPassword(password);
        }

        public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
        {
            return Utilities.Security.Encryption.VerifyPassword(providedPassword, hashedPassword) ?
                                                                PasswordVerificationResult.Success :
                                                                PasswordVerificationResult.Failed;
        }
    }

  سپس باید وارد کلاس ApplicationUserManager شده و در سازنده‌ی آن اینترفیس IPasswordHasher را به عنوان وابستگی تعریف کنید:
public ApplicationUserManager(IUserStore<ApplicationUser, int> store,
            IUnitOfWork uow,
            IIdentity identity,
            IApplicationRoleManager roleManager,
            IDataProtectionProvider dataProtectionProvider,
            IIdentityMessageService smsService,
            IIdentityMessageService emailService, IPasswordHasher passwordHasher)
            : base(store)
        {
            _store = store;
            _uow = uow;
            _identity = identity;
            _users = _uow.Set<ApplicationUser>();
            _roleManager = roleManager;
            _dataProtectionProvider = dataProtectionProvider;
            this.SmsService = smsService;
            this.EmailService = emailService;
            PasswordHasher = passwordHasher;
            createApplicationUserManager();
        }

برای اینکه کلاس IrisPasswordHasher را به عنوان نمونه درخواستی IPasswordHasher معرفی کنیم، باید در تنظیمات StructureMap کد زیر را نیز اضافه کنید:
x.For<IPasswordHasher>().Use<IrisPasswordHasher>();

پیاده سازی اکشن متد ثبت نام کاربر با استفاده از Identity

در کنترلر UserController، اکشن متد Register را به شکل زیر بازنویسی کنید:
        [HttpPost]
        [ValidateAntiForgeryToken]
        [CaptchaVerify("تصویر امنیتی وارد شده معتبر نیست")]
        public virtual async Task<ActionResult> Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                var user = new ApplicationUser
                {
                    CreatedDate = DateAndTime.GetDateTime(),
                    Email = model.Email,
                    IP = Request.ServerVariables["REMOTE_ADDR"],
                    IsBaned = false,
                    UserName = model.UserName,
                    UserMetaData = new UserMetaData(),
                    LastLoginDate = DateAndTime.GetDateTime()
                };

                var result = await _userManager.CreateAsync(user, model.Password);

                if (result.Succeeded)
                {
                    var addToRoleResult = await _userManager.AddToRoleAsync(user.Id, "user");
                    if (addToRoleResult.Succeeded)
                    {
                        var code = await _userManager.GenerateEmailConfirmationTokenAsync(user.Id);
                        var callbackUrl = Url.Action("ConfirmEmail", "User",
                            new { userId = user.Id, code }, protocol: Request.Url.Scheme);

                        _emailService.SendAccountConfirmationEmail(user.Email, callbackUrl);

                        return Json(new { result = "success" });
                    }

                    addErrors(addToRoleResult);
                }

                addErrors(result);
            }

            return PartialView(MVC.User.Views._Register, model);
        }
نکته: در اینجا برای ارسال لینک فعال سازی حساب کاربری، از کلاس EmailService خود سیستم IRIS استفاده شده است؛ نه EmailService مربوط به ASP.NET Identity. همچنین در ادامه نیز از EmailService مربوط به خود سیستم Iris استفاده شده است.

برای این کار متد زیر را به کلاس EmailService  اضافه کنید: 
        public SendingMailResult SendAccountConfirmationEmail(string email, string link)
        {
            var model = new ConfirmEmailModel()
            {
                ActivationLink = link
            };

            var htmlText = _viewConvertor.RenderRazorViewToString(MVC.EmailTemplates.Views._ConfirmEmail, model);

            var result = Send(new MailDocument
            {
                Body = htmlText,
                Subject = "تایید حساب کاربری",
                ToEmail = email
            });

            return result;
        }
همچنین قالب ایمیل تایید حساب کاربری را در مسیر Views/EmailTemplates/_ConfirmEmail.cshtml با محتویات زیر ایجاد کنید:
@model Iris.Model.EmailModel.ConfirmEmailModel

<div style="direction: rtl; -ms-word-wrap: break-word; word-wrap: break-word;">
    <p>با سلام</p>
    <p>برای فعال سازی حساب کاربری خود لطفا بر روی لینک زیر کلیک کنید:</p>
    <p>@Model.ActivationLink</p>
    <div style=" color: #808080;">
        <p>با تشکر</p>
        <p>@Model.SiteTitle</p>
        <p>@Model.SiteDescription</p>
        <p><span style="direction: ltr !important; unicode-bidi: embed;">@Html.ConvertToPersianDateTime(DateTime.Now, "s,H")</span></p>
    </div>
</div>

اصلاح پیام موفقیت آمیز بودن ثبت نام  کاربر جدید


سیستم IRIS از ارسال ایمیل تایید حساب کاربری استفاده نمی‌کند و به محض اینکه عملیات ثبت نام تکمیل می‌شد، صفحه رفرش می‌شود. اما در سیستم Identity یک ایمیل حاوی لینک فعال سازی حساب کاربری به او ارسال می‌شود.
برای اصلاح پیغام پس از ثبت نام، باید به فایل myscript.js درون پوشه‌ی Scripts مراجعه کرده و رویداد onSuccess شیء RegisterUser را به شکل زیراصلاح کنید: 
RegisterUser.Form.onSuccess = function (data) {
    if (data.result == "success") {
        var message = '<div id="alert"><button type="button" data-dismiss="alert">×</button>ایمیلی حاوی لینک فعال سازی، به ایمیل شما ارسال شد؛ لطفا به ایمیل خود مراجعه کرده و بر روی لینک فعال سازی کلیک کنید.</div>';
        $('#registerResult').html(message);
    }
    else {
        $('#logOnModal').html(data);
    }
};
برای تایید ایمیل کاربری که ثبت نام کرده است نیز اکشن متد زیر را به کلاس UserController اضافه کنید:
        [AllowAnonymous]
        public virtual async Task<ActionResult> ConfirmEmail(int? userId, string code)
        {
            if (userId == null || code == null)
            {
                return View("Error");
            }
            var result = await _userManager.ConfirmEmailAsync(userId.Value, code);
            return View(result.Succeeded ? "ConfirmEmail" : "Error");
        }
این اکشن متد نیز احتیاج به View دارد؛ پس view متناظر آن را با محتویات زیر اضافه کنید:
@{
    ViewBag.Title = "حساب کاربری شما تایید شد";
}
<h2>@ViewBag.Title.</h2>
<div>
    <p>
        با تشکر از شما، حساب کاربری شما تایید شد.
    </p>
    <p>
        @Ajax.ActionLink("ورود / ثبت نام", MVC.User.ActionNames.LogOn, MVC.User.Name, new { area = "", returnUrl = Html.ReturnUrl(Context, Url) }, new AjaxOptions { HttpMethod = "GET", InsertionMode = InsertionMode.Replace, UpdateTargetId = "logOnModal", LoadingElementDuration = 300, LoadingElementId = "loadingMessage", OnSuccess = "LogOnForm.onSuccess" }, new { role = "button", data_toggle = "modal", data_i_logon_link = "true", rel = "nofollow" })
    </p>
</div>

اصلاح اکشن متد ورود به سایت 

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async virtual Task<ActionResult> LogOn(LogOnModel model, string returnUrl)
        {
            if (!ModelState.IsValid)
            {
                if (Request.IsAjaxRequest())
                    return PartialView(MVC.User.Views._LogOn, model);
                return View(model);
            }


            const string emailRegPattern =
                @"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$";

            string ip = Request.ServerVariables["REMOTE_ADDR"];

            SignInStatus result = SignInStatus.Failure;

            if (Regex.IsMatch(model.Identity, emailRegPattern))
            {

                var user = await _userManager.FindByEmailAsync(model.Identity);

                if (user != null)
                {
                    result = await _signInManager.PasswordSignInAsync
                   (user.UserName,
                   model.Password, model.RememberMe, shouldLockout: true);
                }
            }
            else
            {
                result = await _signInManager.PasswordSignInAsync(model.Identity, model.Password, model.RememberMe, shouldLockout: true);
            }


            switch (result)
            {
                case SignInStatus.Success:
                    if (Request.IsAjaxRequest())
                        return JavaScript(IsValidReturnUrl(returnUrl)
                            ? string.Format("window.location ='{0}';", returnUrl)
                            : "window.location.reload();");
                    return redirectToLocal(returnUrl);

                case SignInStatus.LockedOut:
                    ModelState.AddModelError("",
                        string.Format("حساب شما قفل شد، لطفا بعد از {0} دقیقه دوباره امتحان کنید.",
                            _userManager.DefaultAccountLockoutTimeSpan.Minutes));
                    break;
                case SignInStatus.Failure:
                    ModelState.AddModelError("", "نام کاربری یا کلمه عبور اشتباه است.");
                    break;
                default:
                    ModelState.AddModelError("", "در ورود شما خطایی رخ داده است.");
                    break;
            }


            if (Request.IsAjaxRequest())
                return PartialView(MVC.User.Views._LogOn, model);
            return View(model);
        }


اصلاح اکشن متد خروج کاربر از سایت

        [HttpPost]
        [ValidateAntiForgeryToken]
        [Authorize]
        public virtual ActionResult LogOut()
        {
            _authenticationManager.SignOut();

            if (Request.IsAjaxRequest())
                return Json(new { result = "true" });

            return RedirectToAction(MVC.User.ActionNames.LogOn, MVC.User.Name);
        }

پیاده سازی ریست کردن کلمه‌ی عبور با استفاده از ASP.NET Identity

مکانیزم سیستم IRIS برای ریست کردن کلمه‌ی عبور به هنگام فراموشی آن، ساخت GUID و ذخیره‌ی آن در دیتابیس است. سیستم Identity  با استفاده از یک توکن رمز نگاری شده و بدون استفاده از دیتابیس، این کار را انجام می‌دهد و با استفاده از قابلیت‌های تو کار سیستم Identity، تمهیدات امنیتی بهتری را نسبت به سیستم کنونی در نظر گرفته است.

برای این کار کدهای کنترلر ForgottenPasswordController را به شکل زیر ویرایش کنید:
using System.Threading.Tasks;
using System.Web.Mvc;
using CaptchaMvc.Attributes;
using Iris.Model;
using Iris.Servicelayer.Interfaces;
using Iris.Web.Email;
using Microsoft.AspNet.Identity;

namespace Iris.Web.Controllers
{
    public partial class ForgottenPasswordController : Controller
    {
        private readonly IEmailService _emailService;
        private readonly IApplicationUserManager _userManager;

        public ForgottenPasswordController(IEmailService emailService, IApplicationUserManager applicationUserManager)
        {
            _emailService = emailService;
            _userManager = applicationUserManager;
        }

        [HttpGet]
        public virtual ActionResult Index()
        {
            return PartialView(MVC.ForgottenPassword.Views._Index);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        [CaptchaVerify("تصویر امنیتی وارد شده معتبر نیست")]
        public async virtual Task<ActionResult> Index(ForgottenPasswordModel model)
        {
            if (!ModelState.IsValid)
            {
                return PartialView(MVC.ForgottenPassword.Views._Index, model);
            }

            var user = await _userManager.FindByEmailAsync(model.Email);
            if (user == null || !(await _userManager.IsEmailConfirmedAsync(user.Id)))
            {
                // Don't reveal that the user does not exist or is not confirmed
                return Json(new
                {
                    result = "false",
                    message = "این ایمیل در سیستم ثبت نشده است"
                });
            }

            var code = await _userManager.GeneratePasswordResetTokenAsync(user.Id);

            _emailService.SendResetPasswordConfirmationEmail(user.UserName, user.Email, code);

            return Json(new
            {
                result = "true",
                message = "ایمیلی برای تایید بازنشانی کلمه عبور برای شما ارسال شد.اعتبارایمیل ارسالی 3 ساعت است."
            });
        }

        [AllowAnonymous]
        public virtual ActionResult ResetPassword(string code)
        {
            return code == null ? View("Error") : View();
        }


        [AllowAnonymous]
        public virtual ActionResult ResetPasswordConfirmation()
        {
            return View();
        }

        //
        // POST: /Account/ResetPassword
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public virtual async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }
            var user = await _userManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                // Don't reveal that the user does not exist
                return RedirectToAction("Error");
            }
            var result = await _userManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
            if (result.Succeeded)
            {
                return RedirectToAction("ResetPasswordConfirmation", "ForgottenPassword");
            }
            addErrors(result);
            return View();
        }

        private void addErrors(IdentityResult result)
        {
            foreach (var error in result.Errors)
            {
                ModelState.AddModelError("", error);
            }
        }

    }
}

همچنین برای اکشن متدهای اضافه شده، View‌های زیر را نیز باید اضافه کنید:

- View  با نام  ResetPasswordConfirmation.cshtml را اضافه کنید.
@{
    ViewBag.Title = "کلمه عبور شما تغییر کرد";
}

<hgroup>
    <h1>@ViewBag.Title.</h1>
</hgroup>
<div>
    <p>
        کلمه عبور شما با موفقیت تغییر کرد
    </p>
    <p>
        @Ajax.ActionLink("ورود / ثبت نام", MVC.User.ActionNames.LogOn, MVC.User.Name, new { area = "", returnUrl = Html.ReturnUrl(Context, Url) }, new AjaxOptions { HttpMethod = "GET", InsertionMode = InsertionMode.Replace, UpdateTargetId = "logOnModal", LoadingElementDuration = 300, LoadingElementId = "loadingMessage", OnSuccess = "LogOnForm.onSuccess" }, new { role = "button", data_toggle = "modal", data_i_logon_link = "true", rel = "nofollow" })
    </p>
</div>

- View با نام ResetPassword.cshtml
@model Iris.Model.ResetPasswordViewModel
@{
    ViewBag.Title = "ریست کردن کلمه عبور";
}
<h2>@ViewBag.Title.</h2>
@using (Html.BeginForm("ResetPassword", "ForgottenPassword", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>ریست کردن کلمه عبور</h4>
    <hr />
    @Html.ValidationSummary("", new { @class = "text-danger" })
    @Html.HiddenFor(model => model.Code)
    <div>
        @Html.LabelFor(m => m.Email, "ایمیل", new { @class = "control-label" })
        <div>
            @Html.TextBoxFor(m => m.Email)
        </div>
    </div>
    <div>
        @Html.LabelFor(m => m.Password, "کلمه عبور", new { @class = "control-label" })
        <div>
            @Html.PasswordFor(m => m.Password)
        </div>
    </div>
    <div>
        @Html.LabelFor(m => m.ConfirmPassword, "تکرار کلمه عبور", new { @class = "control-label" })
        <div>
            @Html.PasswordFor(m => m.ConfirmPassword)
        </div>
    </div>
    <div>
        <div>
            <input type="submit" value="تغییر کلمه عبور" />
        </div>
    </div>
}

همچنین این View و Controller متناظر آن، احتیاج به ViewModel زیر دارند که آن را به پروژه‌ی Iris.Models اضافه کنید.
using System.ComponentModel.DataAnnotations;
namespace Iris.Model
{
    public class ResetPasswordViewModel
    {
        [Required]
        [EmailAddress]
        [Display(Name = "ایمیل")]
        public string Email { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "کلمه عبور باید حداقل 6 حرف باشد", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "کلمه عبور")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "تکرار کلمه عبور")]
        [Compare("Password", ErrorMessage = "کلمه عبور و تکرارش یکسان نیستند")]
        public string ConfirmPassword { get; set; }

        public string Code { get; set; }
    }
}

حذف سیستم قدیمی احراز هویت

برای حذف کامل سیستم احراز هویت IRIS، وارد فایل Global.asax.cs شده و سپس از متد Application_AuthenticateRequest کدهای زیر را حذف کنید:

var principalService = ObjectFactory.GetInstance<IPrincipalService>();
var formsAuthenticationService = ObjectFactory.GetInstance<IFormsAuthenticationService>();
context.User = principalService.GetCurrent()

فارسی کردن خطاهای ASP.NET Identity

سیستم Identity، پیام‌های خطا‌ها را از فایل Resource موجود در هسته‌ی خود، که به طور پیش فرض، زبان آن انگلیسی است، می‌خواند. برای مثال وقتی ایمیلی تکراری باشد، پیامی به زبان انگلیسی دریافت خواهید کرد و متاسفانه برای تغییر آن، راه سر راست و واضحی وجود ندارد. برای تغییر این پیام‌ها می‌توان از سورس باز بودن Identity استفاده کنید و قسمتی را که پیام‌ها را تولید می‌کند، خودتان با پیام‌های فارسی باز نویسی کنید.

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

    public class CustomUserValidator<TUser, TKey> : IIdentityValidator<ApplicationUser>
        where TUser : class, IUser<int>
        where TKey : IEquatable<int>
    {

        public bool AllowOnlyAlphanumericUserNames { get; set; }
        public bool RequireUniqueEmail { get; set; }

        private ApplicationUserManager Manager { get; set; }
        public CustomUserValidator(ApplicationUserManager manager)
        {
            if (manager == null)
                throw new ArgumentNullException("manager");
            AllowOnlyAlphanumericUserNames = true;
            Manager = manager;
        }
        public virtual async Task<IdentityResult> ValidateAsync(ApplicationUser item)
        {
            if (item == null)
                throw new ArgumentNullException("item");
            var errors = new List<string>();
            await ValidateUserName(item, errors);
            if (RequireUniqueEmail)
                await ValidateEmailAsync(item, errors);
            return errors.Count <= 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray());
        }

        private async Task ValidateUserName(ApplicationUser user, ICollection<string> errors)
        {
            if (string.IsNullOrWhiteSpace(user.UserName))
                errors.Add("نام کاربری نباید خالی باشد");
            else if (AllowOnlyAlphanumericUserNames && !Regex.IsMatch(user.UserName, "^[A-Za-z0-9@_\\.]+$"))
            {
                errors.Add("برای نام کاربری فقط از کاراکتر‌های مجاز استفاده کنید ");
            }
            else
            {
                var owner = await Manager.FindByNameAsync(user.UserName);
                if (owner != null && !EqualityComparer<int>.Default.Equals(owner.Id, user.Id))
                    errors.Add("این نام کاربری قبلا ثبت شده است");
            }
        }

        private async Task ValidateEmailAsync(ApplicationUser user, ICollection<string> errors)
        {
            var email = await Manager.GetEmailStore().GetEmailAsync(user).WithCurrentCulture();
            if (string.IsNullOrWhiteSpace(email))
            {
                errors.Add("وارد کردن ایمیل ضروریست");
            }
            else
            {
                try
                {
                    var m = new MailAddress(email);

                }
                catch (FormatException)
                {
                    errors.Add("ایمیل را به شکل صحیح وارد کنید");
                    return;
                }
                var owner = await Manager.FindByEmailAsync(email);
                if (owner != null && !EqualityComparer<int>.Default.Equals(owner.Id, user.Id))
                    errors.Add("این ایمیل قبلا ثبت شده است");
            }
        }
    }

    public class CustomPasswordValidator : IIdentityValidator<string>
    {
        #region Properties
        public int RequiredLength { get; set; }
        public bool RequireNonLetterOrDigit { get; set; }
        public bool RequireLowercase { get; set; }
        public bool RequireUppercase { get; set; }
        public bool RequireDigit { get; set; }
        #endregion

        #region IIdentityValidator
        public virtual Task<IdentityResult> ValidateAsync(string item)
        {
            if (item == null)
                throw new ArgumentNullException("item");
            var list = new List<string>();

            if (string.IsNullOrWhiteSpace(item) || item.Length < RequiredLength)
                list.Add(string.Format("کلمه عبور نباید کمتر از 6 کاراکتر باشد"));

            if (RequireNonLetterOrDigit && item.All(IsLetterOrDigit))
                list.Add("برای امنیت بیشتر از حداقل از یک کارکتر غیر عددی و غیر حرف  برای کلمه عبور استفاده کنید");

            if (RequireDigit && item.All(c => !IsDigit(c)))
                list.Add("برای امنیت بیشتر از اعداد هم در کلمه عبور استفاده کنید");
            if (RequireLowercase && item.All(c => !IsLower(c)))
                list.Add("از حروف کوچک نیز برای کلمه عبور استفاده کنید");
            if (RequireUppercase && item.All(c => !IsUpper(c)))
                list.Add("از حروف بزرک نیز برای کلمه عبور استفاده کنید");
            return Task.FromResult(list.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(string.Join(" ", list)));
        }

        #endregion

        #region PrivateMethods
        public virtual bool IsDigit(char c)
        {
            if (c >= 48)
                return c <= 57;
            return false;
        }

        public virtual bool IsLower(char c)
        {
            if (c >= 97)
                return c <= 122;
            return false;
        }


        public virtual bool IsUpper(char c)
        {
            if (c >= 65)
                return c <= 90;
            return false;
        }

        public virtual bool IsLetterOrDigit(char c)
        {
            if (!IsUpper(c) && !IsLower(c))
                return IsDigit(c);
            return true;
        }
        #endregion

    }

سپس باید کلاس‌های فوق را به Identity معرفی کنید تا از این کلاس‌های سفارشی شده به جای کلاس‌های پیش فرض خودش استفاده کند. برای این کار وارد کلاس ApplicationUserManager شده و درون متد createApplicationUserManager کدهای زیر را اضافه کنید: 
            UserValidator = new CustomUserValidator< ApplicationUser, int>(this)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            PasswordValidator = new CustomPasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = false,
                RequireUppercase = false
            };
روش دیگر مراجعه به سورس ASP.NET Identity است. با مراجعه به مخزن کد آن، فایل Resources.resx آن را که حاوی متن‌های خطا به زبان انگلیسی است، درون پروژه‌ی خود کپی کنید. همچین کلاس‌های UserValidator و PasswordValidator را نیز درون پروژه کپی کنید تا این کلاس‌ها از فایل Resource موجود در پروژه‌ی خودتان استفاده کنند. در نهایت همانند روش قبلی درون متد createApplicationUserManager کلاس ApplicationUserManager، کلاس‌های UserValidator و PasswordValidator را به Identity معرفی کنید.


ایجاد SecurityStamp برای کاربران فعلی سایت

سیستم Identity برای لحاظ کردن یک سری موارد امنیتی، به ازای هر کاربر، فیلدی را به نام SecurityStamp درون دیتابیس ذخیره می‌کند و برای این که این سیستم عملکرد صحیحی داشته باشد، باید این مقدار را برای کاربران فعلی سایت ایجاد کرد تا کاربران فعلی بتوانند از امکانات Identity نظیر فراموشی کلمه عبور، ورود به سیستم و ... استفاده کنند.
برای این کار Identity، متدی به نام UpdateSecurityStamp را در اختیار قرار می‌دهد تا با استفاده از آن بتوان مقدار فیلد SecurityStamp را به روز رسانی کرد.
معمولا برای انجام این کارها می‌توانید یک کنترلر تعریف کنید و درون اکشن متد آن کلیه‌ی کاربران را واکشی کرده و سپس متد UpdateSecurityStamp را بر روی آن‌ها فراخوانی کنید.
        public virtual async Task<ActionResult> UpdateAllUsersSecurityStamp()
        {
            foreach (var user in await _userManager.GetAllUsersAsync())
            {
                await _userManager.UpdateSecurityStampAsync(user.Id);
            }
            return Content("ok");
        }
البته این روش برای تعداد زیاد کاربران کمی زمان بر است.


انتقال نقش‌های کاربران به جدول جدید و برقراری رابطه بین آن‌ها

در سیستم Iris رابطه‌ی بین کاربران و نقش‌ها یک به چند بود. در سیستم Identity این رابطه چند به چند است و من به عنوان یک حرکت خوب و رو به جلو، رابطه‌ی چند به چند را در سیستم جدید انتخاب کردم. اکنون با استفاده از دستورات زیر به راحتی می‌توان نقش‌های فعلی و رابطه‌ی بین آن‌ها را به جداول جدیدشان منتقل کرد:
        public virtual async Task<ActionResult> CopyRoleToNewTable()
        {
            var dbContext = new IrisDbContext();

            foreach (var role in await dbContext.Roles.ToListAsync())
            {
                await _roleManager.CreateAsync(new CustomRole(role.Name)
                {
                    Description = role.Description
                });
            }

            var users = await dbContext.Users.Include(u => u.Role).ToListAsync();

            foreach (var user in users)
            {
                await _userManager.AddToRoleAsync(user.Id, user.Role.Name);
            }
            return Content("ok");
        }
البته اجرای این کد نیز برای تعداد زیادی کاربر، زمانبر است؛ ولی روشی مطمئن و دقیق است.
اشتراک‌ها
قواعد نام گذاری در جاوا اسکریپت

رعایت استانداردهای نام‌گذاری هر زبان برنامه‌نویسی یکی از اولین معیارهای برنامه‌نویس حرفه‌ای بودنه. چه وقتی تیمی کار می‌کنیم، چه حتی وقتی که تنها کار می‌کنیم، رعایت استانداردها کمک میکنه که کدهامون قابل فهم‌تر باشه و بتونیم در آینده راحت‌تر اونها رو پشتیبانی کنیم. 

قواعد نام گذاری در جاوا اسکریپت
اشتراک‌ها
دوره آموزشی TypeScript ،ASP.NET Web API ، AngularJS Bootcamp هفته چهارم

This past week of the bootcamp was devoted to the Microsoft ASP.NET Web API. In other words, students learned how to build the server-side half of their full stack Web apps. By this point in the camp, students understand how all of the various pieces of a modern web app fit together. 

دوره آموزشی TypeScript ،ASP.NET Web API ، AngularJS Bootcamp هفته چهارم
اشتراک‌ها
NET 8 RC1. منتشر شد

.NET 8 RC1 is now available. This is our first of two release candidates. This release includes a new AOT mode for both Android and WASM, System.Text.Json improvements, and Azure Managed Identity support for containers. Now is great time to pick up and test .NET 8 if you haven’t yet.  

NET 8 RC1. منتشر شد
اشتراک‌ها
توسعه تطابق الگو با C#8

C# 7 introduced pattern matching with the extension of the switch statement and the is operator offering the const pattern, the type pattern, and the var pattern. With C# 8 an extension of pattern matching is planned, including the property pattern, the recursive pattern, and a new switch – the switch expression. 

توسعه تطابق الگو با C#8