مطالب
تولید هدرهای Content Security Policy توسط ASP.NET Core برای برنامه‌های Angular
پیشتر مطلب «افزودن هدرهای Content Security Policy به برنامه‌های ASP.NET» را در این سایت مطالعه کرده‌اید. در اینجا قصد داریم معادل آن‌را برای ASP.NET Core تهیه کرده و همچنین نکات مرتبط با برنامه‌های Angular را نیز در آن لحاظ کنیم.


تهیه میان افزار افزودن هدرهای Content Security Policy

کدهای کامل این میان افزار را در ادامه مشاهده می‌کنید:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

namespace AngularTemplateDrivenFormsLab.Utils
{
    public class ContentSecurityPolicyMiddleware
    {
        private readonly RequestDelegate _next;

        public ContentSecurityPolicyMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public Task Invoke(HttpContext context)
        {
            context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
            context.Response.Headers.Add("X-Xss-Protection", "1; mode=block");
            context.Response.Headers.Add("X-Content-Type-Options", "nosniff");

            string[] csp =
            {
              "default-src 'self'",
              "style-src 'self' 'unsafe-inline'",
              "script-src 'self' 'unsafe-inline' 'unsafe-eval'",
              "font-src 'self'",
              "img-src 'self' data:",
              "connect-src 'self'",
              "media-src 'self'",
              "object-src 'self'",
              "report-uri /api/CspReport/Log" //TODO: Add api/CspReport/Log
            };
            context.Response.Headers.Add("Content-Security-Policy", string.Join("; ", csp));
            return _next(context);
        }
    }

    public static class ContentSecurityPolicyMiddlewareExtensions
    {
        /// <summary>
        /// Make sure you add this code BEFORE app.UseStaticFiles();,
        /// otherwise the headers will not be applied to your static files.
        /// </summary>
        public static IApplicationBuilder UseContentSecurityPolicy(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<ContentSecurityPolicyMiddleware>();
        }
    }
}
که نحوه‌ی استفاده از آن در کلاس آغازین برنامه به صورت ذیل خواهد بود:
public void Configure(IApplicationBuilder app)
{
   app.UseContentSecurityPolicy();

توضیحات تکمیلی

افزودن X-Frame-Options
 context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
از هدر X-FRAME-OPTIONS، جهت منع نمایش و رندر سایت جاری، در iframeهای سایت‌های دیگر استفاده می‌شود. ذکر مقدار SAMEORIGIN آن، به معنای مجاز تلقی کردن دومین جاری برنامه است.


افزودن X-Xss-Protection
 context.Response.Headers.Add("X-Xss-Protection", "1; mode=block");
تقریبا تمام مرورگرهای امروزی قابلیت تشخیص حملات XSS را توسط static analysis توکار خود دارند. این هدر، آنالیز اجباری XSS را فعال کرده و همچنین تنظیم حالت آن به block، نمایش و رندر قسمت مشکل‌دار را به طور کامل غیرفعال می‌کند.


افزودن X-Content-Type-Options
 context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
وجود این هدر سبب می‌شود تا مرورگر، حدس‌زدن نوع فایل‌ها، درخواست‌ها و محتوا را کنار گذاشته و صرفا به content-type ارسالی توسط سرور اکتفا کند. به این ترتیب برای مثال امکان لینک کردن یک فایل غیرجاوا اسکریپتی و اجرای آن به صورت کدهای جاوا اسکریپت، چون توسط تگ script ذکر شده‌است، غیرفعال می‌شود. در غیراینصورت مرورگر هرچیزی را که توسط تگ script به صفحه لینک شده باشد، صرف نظر از content-type واقعی آن، اجرا خواهد کرد.


افزودن Content-Security-Policy
string[] csp =
            {
              "default-src 'self'",
              "style-src 'self' 'unsafe-inline'",
              "script-src 'self' 'unsafe-inline' 'unsafe-eval'",
              "font-src 'self'",
              "img-src 'self' data:",
              "connect-src 'self'",
              "media-src 'self'",
              "object-src 'self'",
              "report-uri /api/CspReport/Log" //TODO: Add api/CspReport/Log
            };
context.Response.Headers.Add("Content-Security-Policy", string.Join("; ", csp));
وجود این هدر، تزریق کدها و منابع را از دومین‌های دیگر غیرممکن می‌کند. برای مثال ذکر self در اینجا به معنای مجاز بودن الصاق و اجرای اسکریپت‌ها، شیوه‌نامه‌ها، تصاویر و اشیاء، صرفا از طریق دومین جاری برنامه است و هرگونه منبعی که از دومین‌های دیگر به برنامه تزریق شود، قابلیت اجرایی و یا نمایشی نخواهد داشت.

در اینجا ذکر unsafe-inline و unsafe-eval را مشاهده می‌کنید. برنامه‌های Angular به همراه شیوه‌نامه‌های inline و یا بکارگیری متد eval در مواردی خاص هستند. اگر این دو گزینه ذکر و فعال نشوند، در کنسول developer مرورگر، خطای بلاک شدن آن‌ها را مشاهده کرده و همچنین برنامه از کار خواهد افتاد.

یک نکته: با فعالسازی گزینه‌ی aot-- در حین ساخت برنامه، می‌توان unsafe-eval را نیز حذف کرد.


استفاده از فایل web.config برای تعریف SameSite Cookies

یکی از پیشنهادهای اخیر ارائه شده‌ی جهت مقابله‌ی با حملات CSRF و XSRF، قابلیتی است به نام  Same-Site Cookies. به این ترتیب مرورگر، کوکی سایت جاری را به همراه یک درخواست ارسال آن به سایت دیگر، پیوست نمی‌کند (کاری که هم اکنون با درخواست‌های Cross-Site صورت می‌گیرد). برای رفع این مشکل، با این پیشنهاد امنیتی جدید، تنها کافی است SameSite، به انتهای کوکی اضافه شود:
 Set-Cookie: sess=abc123; path=/; SameSite

نگارش‌های بعدی ASP.NET Core، ویژگی SameSite را نیز به عنوان CookieOptions لحاظ کرده‌اند. همچنین یک سری از کوکی‌های خودکار تولیدی توسط آن مانند کوکی‌های anti-forgery به صورت خودکار با این ویژگی تولید می‌شوند.
اما مدیریت این مورد برای اعمال سراسری آن، با کدنویسی میسر نیست (مگر اینکه مانند نگارش‌های بعدی ASP.NET Core پشتیبانی توکاری از آن صورت گیرد). به همین جهت می‌توان از ماژول URL rewrite مربوط به IIS برای افزودن ویژگی SameSite به تمام کوکی‌های تولید شده‌ی توسط سایت، کمک گرفت. برای این منظور تنها کافی است فایل web.config را ویرایش کرده و موارد ذیل را به آن اضافه کنید:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <outboundRules>
        <clear />
        <!-- https://scotthelme.co.uk/csrf-is-dead/ -->
        <rule name="Add SameSite" preCondition="No SameSite">
          <match serverVariable="RESPONSE_Set_Cookie" pattern=".*" negate="false" />
          <action type="Rewrite" value="{R:0}; SameSite=lax" />
          <conditions></conditions>
        </rule>
        <preConditions>
          <preCondition name="No SameSite">
            <add input="{RESPONSE_Set_Cookie}" pattern="." />
            <add input="{RESPONSE_Set_Cookie}" pattern="; SameSite=lax" negate="true" />
          </preCondition>
        </preConditions>
      </outboundRules>
    </rewrite>
  </system.webServer>
</configuration>


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

اگر به هدر Content-Security-Policy دقت کنید، گزینه‌ی آخر آن، ذکر اکشن متدی در سمت سرور است:
   "report-uri /api/CspReport/Log" //TODO: Add api/CspReport/Log
با تنظیم این مورد، می‌توان موارد بلاک شده را در سمت سرور لاگ کرد. اما این اطلاعات ارسالی به سمت سرور، فرمت خاصی را دارند:
{
  "csp-report": {
    "document-uri": "http://localhost:5000/untypedSha",
    "referrer": "",
    "violated-directive": "script-src",
    "effective-directive": "script-src",
    "original-policy": "default-src 'self'; style-src 'self'; script-src 'self'; font-src 'self'; img-src 'self' data:; connect-src 'self'; media-src 'self'; object-src 'self'; report-uri /api/Home/CspReport",
    "disposition": "enforce",
    "blocked-uri": "eval",
    "line-number": 21,
    "column-number": 8,
    "source-file": "http://localhost:5000/scripts.bundle.js",
    "status-code": 200,
    "script-sample": ""
  }
}
به همین جهت ابتدا نیاز است توسط JsonProperty کتابخانه‌ی JSON.NET، معادل این خواص را تولید کرد:
    class CspPost
    {
        [JsonProperty("csp-report")]
        public CspReport CspReport { get; set; }
    }

    class CspReport
    {
        [JsonProperty("document-uri")]
        public string DocumentUri { get; set; }

        [JsonProperty("referrer")]
        public string Referrer { get; set; }

        [JsonProperty("violated-directive")]
        public string ViolatedDirective { get; set; }

        [JsonProperty("effective-directive")]
        public string EffectiveDirective { get; set; }

        [JsonProperty("original-policy")]
        public string OriginalPolicy { get; set; }

        [JsonProperty("disposition")]
        public string Disposition { get; set; }

        [JsonProperty("blocked-uri")]
        public string BlockedUri { get; set; }

        [JsonProperty("line-number")]
        public int LineNumber { get; set; }

        [JsonProperty("column-number")]
        public int ColumnNumber { get; set; }

        [JsonProperty("source-file")]
        public string SourceFile { get; set; }

        [JsonProperty("status-code")]
        public string StatusCode { get; set; }

        [JsonProperty("script-sample")]
        public string ScriptSample { get; set; }
    }
اکنون می‌توان بدنه‌ی درخواست را استخراج و سپس به این شیء ویژه نگاشت کرد:
namespace AngularTemplateDrivenFormsLab.Controllers
{
    [Route("api/[controller]")]
    public class CspReportController : Controller
    {
        [HttpPost("[action]")]
        [IgnoreAntiforgeryToken]
        public async Task<IActionResult> Log()
        {
            CspPost cspPost;
            using (var bodyReader = new StreamReader(this.HttpContext.Request.Body))
            {
                var body = await bodyReader.ReadToEndAsync().ConfigureAwait(false);
                this.HttpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body));
                cspPost = JsonConvert.DeserializeObject<CspPost>(body);
            }

            //TODO: log cspPost

            return Ok();
        }
    }
}
در اینجا نحوه‌ی استخراج Request.Body را به صورت خام را مشاهده می‌کنید. سپس توسط متد DeserializeObject کتابخانه‌ی JSON.NET، این رشته به شیء CspPost نگاشت شده‌است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
مطالب
نحوه ایجاد فیلدهای محاسباتی در PdfReport
در گزارشات، گاهی از اوقات نیاز خواهد شد تا تعدادی ستون جدید را بر اساس مقادیر فیلدهای موجود در منبع داده گزارش، به صورت پویا محاسبه و تولید کنیم (ایجاد ستون‌هایی که در منبع داده وجود خارجی ندارند). در ادامه با نحوه انجام اینکار در PdfReport آشنا خواهیم شد.
در ابتدا کدهای کامل گزارش این قسمت را در ادامه ملاحظه خواهید کرد (در این کلاس از دو کلاس User و AppPath قسمت قبل نیز استفاده شده است):
using System;
using System.Collections.Generic;
using PdfReportSamples.Models;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.CalculatedFields
{
    public class CalculatedFieldsPdfReport
    {
        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.RightToLeft);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
            })
             .DefaultFonts(fonts =>
             {
                 fonts.Path(AppPath.ApplicationPath + "\\fonts\\irsans.ttf",
                            Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
             })
             .PagesFooter(footer =>
             {
                 footer.DefaultFooter(printDate: DateTime.Now.ToString("MM/dd/yyyy"));
             })
             .PagesHeader(header =>
             {
                 header.DefaultHeader(defaultHeader =>
                 {
                     defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
                     defaultHeader.Message("گزارش جدید ما");
                 });
             })
             .MainTableTemplate(template =>
             {
                 template.BasicTemplate(BasicTemplate.RainyDayTemplate);
             })
             .MainTablePreferences(table =>
             {
                 table.ColumnsWidthsType(TableColumnWidthType.Relative);
             })
             .MainTableDataSource(dataSource =>
             {
                 var listOfRows = new List<User>();
                 for (int i = 0; i < 220; i++)
                 {
                     listOfRows.Add(new User { Id = i, LastName = "نام خانوادگی " + i, Name = "نام " + i, Balance = i + 1000 });
                 }
                 dataSource.StronglyTypedList<User>(listOfRows);
             })
             .MainTableEvents(events =>
             {
                 events.DataSourceIsEmpty(message: "رکوردی یافت نشد.");
             })
             .MainTableSummarySettings(summary =>
             {
                 summary.OverallSummarySettings("جمع");
                 summary.PreviousPageSummarySettings("نقل از صفحه قبل");
                 summary.PageSummarySettings("جمع صفحه");
             })
             .MainTableColumns(columns =>
             {
                 columns.AddColumn(column =>
                 {
                     column.PropertyName("rowNo");
                     column.IsRowNumber(true);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(0);
                     column.Width(1);
                     column.HeaderCell("ردیف", captionRotation: 90);
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName<User>(x => x.Id);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(1);
                     column.Width(2);
                     column.HeaderCell("شماره");
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName<User>(x => x.Name);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(2);
                     column.Width(2);
                     column.HeaderCell("نام");
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName<User>(x => x.LastName);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(3);
                     column.Width(3);
                     column.HeaderCell("نام خانوادگی");
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName("CF1");
                     column.CalculatedField(true,
                         list =>
                         {
                             if (list == null) return string.Empty;
                             var name = list.GetSafeStringValueOf<User>(x => x.Name);
                             var lastName = list.GetSafeStringValueOf<User>(x => x.LastName);
                             return name + " - " + lastName;
                         });
                     column.HeaderCell("ف.م.");
                     column.Width(3);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(4);
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName<User>(x => x.Balance);
                     column.HeaderCell("موجودی");
                     column.ColumnItemsTemplate(template =>
                     {
                         template.TextBlock();
                         template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.Width(2);
                     column.AggregateFunction(aggregateFunction =>
                     {
                         aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
                         aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(5);
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName("CF2");
                     column.HeaderCell("ف.م.");
                     column.Width(3);
                     column.AggregateFunction(aggregateFunction =>
                     {
                         aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
                         aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.ColumnItemsTemplate(template =>
                     {
                         template.TextBlock();
                         template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.CalculatedField(true,
                         list =>
                         {
                             if (list == null) return string.Empty;
                             var balance = list.GetValueOf<User>(x => x.Balance);
                             return (long)balance * 3;
                         });
                     column.IsVisible(true);
                     column.Order(6);
                 });
             })
             .Export(export =>
             {
                 export.ToExcel(footer: "Footer text", header: "&24&U&\"Arial,Regular Bold\" New rpt.", pageLayoutView: true);
                 export.ToXml();
             })
             .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptCalculatedFieldsSample.pdf"));
        }
    }
}

توضیحات:
- در این مثال از یک قلم سفارشی به نام Iranian Sans استفاده شده که در پوشه bin\fonts سورس به روز شده پروژه، قابل دریافت است.
- برخلاف قسمت قبل، از متد table.NumberOfDataRowsPerPage استفاده نشده است. به این ترتیب تعداد ردیف‌ها به صورت خودکار بر اساس اندازه صفحه محاسبه می‌شود.
- منبع داده تعریف شده توسط  dataSource.StronglyTypedList، بسیار مناسب است جهت کار با انواع و اقسام ORMها. تفاوتی نمی‌کند از چه ORM ایی استفاده می‌کنید؛ همینقدر که خروجی کار شما یک List باشد، در اینجا قابل استفاده خواهد بود. حتی نیازی هم به بانک اطلاعاتی نیست و در این مثال از یک منبع داده درون حافظه‌ای استفاده شده است.
- توضیحاتی در مورد ColumnsWidthsType :
برای تعیین عرض ستون‌ها، چهار حالت بر اساس مقادیر enum ایی به نام TableColumnWidthType میسر است:
الف) Relative : عرض نسبی. به این معنا که اگر سه ستون با عرض‌های 2, 1, 1 تعریف کنید، کل عرض صفحه به 4 قسمت تقسیم می‌شود. از این 4 قسمت، 2 قسمت به ستون اول و یک قسمت به ستون دوم و همچنین یک قسمت به ستون سوم اختصاص خواهد یافت.
ب) Absolute : در این حالت باید عرض ستون‌ها را دقیقا بر اساس user space units مشخص کنید.
ج) FitToContent : سعی خواهد کرد بر اساس طول محتوای یک سلول، عرض بهینه‌ای را محاسبه کند. در این حالت نیازی به قید column.Width نیست.
د) EquallySized : به صورت خودکار عرض تمام ستون‌ها را یکسان محاسبه می‌کند. در این حالت نیازی به قید column.Width نیست.
- اولین فیلد محاسباتی که در PdfReport به صورت توکار و خودکار در اختیار شما است، فیلد شماره ردیف می‌باشد که به صورت زیر مشخص می‌شود:
 column.IsRowNumber(true);
بنابراین نیازی نیست تا منبع داده شما شامل یک ستون اضافی به نام ردیف باشد. PdfReport این مورد را به صورت خودکار تولید خواهد کرد.
- سپس دو فیلد و ستون محاسباتی در گزارش فوق قابل مشاهده هستند:
                 columns.AddColumn(column =>
                 {
                     column.PropertyName("CF1");
                     column.CalculatedField(true,
                         list =>
                         {
                             if (list == null) return string.Empty;
                             var name = list.GetSafeStringValueOf<User>(x => x.Name);
                             var lastName = list.GetSafeStringValueOf<User>(x => x.LastName);
                             return name + " - " + lastName;
                         });
                     column.HeaderCell("ف.م.");
                     column.Width(3);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(4);
                 });
برای اینکه یک فیلد پویا را به مجموعه فیلدهای مهیا شده توسط منبع داده اضافه کنیم، از متد CalculatedField با پارامتر اول مساوی true استفاده خواهیم کرد. سپس نیاز است نحوه محاسبه این فیلد را مشخص کنیم. امضای متد CalculatedField به صورت زیر است:
 public void CalculatedField(bool isCalculatedField, Func<IList<CellData>, object> calculatedFieldFormula)
به این معنا که توسط آرگومان دوم آن، لیست کلیه مقادیر ردیف جاری در اختیار شما قرار خواهد گرفت. در این بین فرصت خواهیم داشت بر این اساس، فیلد و مقدار جدیدی را تولید کرده و بازگشت دهیم؛ که نمونه‌ای از اینکار را در فیلد محاسبانی CF1 فوق مشاهده می‌کنید.
باید دقت داشت که نام خواص (column.PropertyName) باید منحصربفرد باشند و گرنه برنامه با یک استثناء متوقف خواهد شد. اگر ستون معرفی شده متناظر است با یک فیلد یا خاصیت منبع داده، باید PropertyName با رعایت کوچکی و بزرگی حروف، معادل فیلد متناظر باشد. اگر ستون تعریف شده یک فیلد محاسباتی است، تنها کافی است یک نام دلخواه غیرتکراری را ذکر کرد.
همچنین جهت سهولت کار، در فضای نام PdfRpt.Core.Helper، تعدادی متد برای کار با لیستی از CellDataها تدارک دیده شده‌اند؛ که نمونه‌ای از آن‌را در اینجا با استفاده از متدهای GetSafeStringValueOf ملاحظه می‌کنید.

- فیلد محاسباتی دیگری نیز در این گزارش قابل مشاهده است:
                 columns.AddColumn(column =>
                 {
                     column.PropertyName("CF2");
                     column.HeaderCell("ف.م.");
                     column.Width(3);
                     column.AggregateFunction(aggregateFunction =>
                     {
                         aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
                         aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.ColumnItemsTemplate(template =>
                     {
                         template.TextBlock();
                         template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.CalculatedField(true,
                         list =>
                         {
                             if (list == null) return string.Empty;
                             var balance = list.GetValueOf<User>(x => x.Balance);
                             return (long)balance * 3;
                         });
                     column.IsVisible(true);
                     column.Order(6);
                 });
در اینجا در متد  column.CalculatedField، مقدار فیلد موجودی (Balance) ردیف جاری دریافت شده و سپس مقدار دلخواه جدیدی تولید و بازگشت داده شده است.
همچنین توسط متد column.AggregateFunction، مشخص کرده‌ایم که این ستون جدید نیاز به جمع پایین صفحه هم دارد. به علاوه توسط متد column.ColumnItemsTemplate، با مشخص سازی DisplayFormatFormula، پیش از نمایش اطلاعات فیلد جاری، مقدار آن‌را دریافت و فرمت کرده‌ایم؛ که در اینجا سه رقم جداکننده اعداد اضافه شده است.
ذکر متد template.TextBlock اختیاری است و حالت پیش فرض می‌باشد. قالب‌های دیگری نیز در اینجا تعریف شده‌اند که در مثال‌های قسمت‌های بعدی آن‌ها را بررسی خواهیم کرد (امکان نمایش تصویر، لینک، بارکد و غیره).

 
مطالب
ویرایش قالب پیش فرض Add View در ASP.NET MVC برای سازگار سازی آن با Twitter bootstrap
همانطور که در مطلب «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» ملاحظه کردید، برای سازگار سازی یک فرم جدید ایجاد شده ASP.NET MVC با پیش فرض‌های Twitter bootstrap، حداقل 8 مرحله باید طی شود و ... چقدر خوب می‌شد اگر این‌کارها به صورت خودکار توسط VS.NET بجای قالب پیش فرض ایجاد فرم آن، تولید می‌شد. در ادامه قصد داریم این سفارشی سازی را انجام دهیم.


مراحل کلی سفارشی سازی قالب‌های Scaffolding پیش فرض ASP.NET MVC

قالب‌های Scaffolding پیش فرض ASP.NET در مسیر Microsoft Visual Studio X\Common7\IDE\ItemTemplates\CSharp\Web\MVC X\CodeTemplates قرار دارند. برای نمونه اگر بخواهیم پیش فرض‌های تولید فرم‌های MVC4 را تغییر دهیم، باید به پوشه MVC 4\CodeTemplates\AddView\CSHTML مراجعه و فایل Create.tt را ویرایش کنیم.
اینکار هرچند عملی است اما آنچنان جالب نیست؛ از این جهت که تاثیری کلی و سراسری خواهد داشت.
برای اعمال محلی این تغییرات فقط به یک پروژه خاص، تنها کافی است همین مسیر CodeTemplates\AddView\CSHTML به همراه تمام فایل‌های tt آن، در پوشه جاری پروژه مدنظر ما کپی شود. به این ترتیب ابتدا به این پوشه محلی مراجعه خواهد شد.
روش دوم کپی کردن این فایل‌ها، استفاده از بسته نیوگت ذیل است:
 PM> Install-Package Mvc4CodeTemplatesCSharp


سفارشی سازی فایل Create.tt پیش فرض ASP.NET MVC جهت سازگار سازی آن با Twitter bootstrap

در اینجا قصد داریم همان 8 مرحله مطلب «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» را به فایل Create.tt که اکنون در پوشه CodeTemplates\AddView\CSHTML\Create.tt ریشه پروژه جاری قرار دارد، اعمال کنیم.
الف) ابتدا نام این فایل را به CreateBootstrapForm.tt تغییر می‌دهیم. از این لحاظ که این نام جدید در drop down مرتبط با scaffold template صفحه Add view ظاهر خواهد شد. به علاوه نیازی نیست تا این فایل tt در همان لحظه اجرا شود، بنابراین به خواص آن در VS.NET مراجعه کرده و مقدار گزینه custom tool آن‌را خالی می‌کنیم (مانند سایر فایل‌های tt اضافه شده).
ب) قسمت ابتدایی فایل CreateBootstrapForm.tt را که همان کپی مطابق اصل فایل Create.tt است، به نحو ذیل تغییر می‌دهیم:
<#
    if (!mvcHost.IsContentPage) {
#>
<script src="~/Scripts/jquery-1.9.1.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>

<#
    }
}
#>
@using (Html.BeginForm()) {
    @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" })

    <fieldset class="form-horizontal">
        <legend><#= mvcHost.ViewDataType.Name #></legend>

<#
foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {
    if (!property.IsPrimaryKey && !property.IsReadOnly && property.Scaffold) {
#>
        <div class="control-group">
<#
        if (property.IsForeignKey) {
#>
            @Html.LabelFor(model => model.<#= property.Name #>, "<#= property.AssociationName #>",new {@class="control-label"})
<#
        } else {
#>
            @Html.LabelFor(model => model.<#= property.Name #>,new {@class="control-label"})
<#
        }
#>
        
           <div class="controls">
<#
        if (property.IsForeignKey) {
#>
            @Html.DropDownList("<#= property.Name #>", String.Empty)
<#
        } else {
#>
            @Html.EditorFor(model => model.<#= property.Name #>)
<#
        }
#>
            @Html.ValidationMessageFor(model => model.<#= property.Name #>,null,new{@class="help-inline"})
</div>
        </div>

<#
    }
}
#>
<div class="form-actions">
            <button type="submit" class="btn btn-primary">ارسال</button>
            <button class="btn">لغو</button>
          </div>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>
<#
if(mvcHost.IsContentPage && mvcHost.ReferenceScriptLibraries) {
#>

@section JavaScript {    

}
که حاصل آن به صورت ذیل قابل استفاده و دسترسی خواهد بود:


دریافت فایل CreateBootstrapForm.tt اصلاح شده:
همانطور که عنوان شد، برای استفاده از آن فقط کافی است آن‌را در مسیر CodeTemplates\AddView\CSHTML\CreateBootstrapForm.tt ریشه پروژه جاری خود کپی کنید.
مطالب
محاسبه مجدد میزان مصرف حافظه‌ی برنامه‌های دات نت

اگر به میزان مصرف حافظه‌ اولیه‌ی برنامه‌های دات نت دقت کنیم، نسبت به مثلا یک برنامه‌ی MFC چند برابر به نظر می‌رسند و ... این علت دارد:
زمانیکه یک برنامه‌ی مبتنی بر دات نت اجرا می‌شود، ابتدا JIT compiler شروع به کار کرده و شروع به کامپایل برنامه می‌کند. این بارگزاری هم در همان پروسه‌ی اصلی برنامه انجام می‌شود. به همین جهت میزان مصرف حافظه‌ی برنامه‌های دات نت عموما بالا به نظر می‌رسد.
اکنون سؤال اینجا است که آیا می توان این حافظه‌ای را که دیگر مورد استفاده نیست (و توسط JIT compiler اخذ شده) به سیستم بازگرداند و محاسبه‌ی مجددی را در این مورد انجام داد. پاسخ به این سؤال را در متد ReEvaluateWorkingSet زیر می‌توان مشاهده کرد:


using System;
using System.Diagnostics;

namespace Toolkit
{
public static class Memory
{
public static void ReEvaluateWorkingSet()
{
try
{
Process loProcess = Process.GetCurrentProcess();
//it doesn't matter what you set maxWorkingSet to
//setting it to any value apparently causes the working set to be re-evaluated and excess discarded
loProcess.MaxWorkingSet = (IntPtr)((int)loProcess.MaxWorkingSet + 1);
}
catch
{
//The above code requires Admin privileges.
//So it's important to trap exceptions in case you're running without admin rights.
}
}
}
}

در این متد ابتدا پروسه جاری دریافت شده و سپس MaxWorkingSet به یک عدد دلخواه تنظیم می‌شود. مهم نیست که این عدد چه چیزی باشد، زیرا این تنظیم سبب می‌شود که در پشت صحنه به شکل حساب شده‌ای حافظه‌ای که مورد استفاده نیست به سیستم بازگردانده شود و سپس عددی که در task manager نمایش داده می‌شود، مجددا محاسبه گردد. همچنین باید دقت داشت که این کد تنها با دسترسی مدیریتی قابل اجرا است و به همین دلیل وجود این try/catch ضروری است.

نحوه استفاده از متد ReEvaluateWorkingSet در برنامه‌های WinForms :
فایل Program.cs را یافته و سپس در روال رویداد گردان Idle برنامه، متد ReEvaluateWorkingSet را فراخوانی کنید (مثلا هر زمان که برنامه minimized شد اجرا می‌شود):

//Program.cs
namespace MemUsage
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
//...

Application.Idle += applicationIdle;
}

static void applicationIdle(object sender, EventArgs e)
{
Memory.ReEvaluateWorkingSet();
}
}
}

نحوه استفاده از متد ReEvaluateWorkingSet در برنامه‌های WPF :
فایل App.xaml.cs را یافته و سپس در روال رویدادگردان Deactivated برنامه، متد ReEvaluateWorkingSet را فراخوانی کنید:

//App.xaml.cs

public App()
{
this.Deactivated += appDeactivated;
}

void appDeactivated(object sender, EventArgs e)
{
Memory.ReEvaluateWorkingSet();
}

تاثیر آن هم قابل ملاحظه است (حداقل از لحاظ روانی!). تست کنید!

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

در این آموزش قصد دارم نحوه ایجاد یک Slideshow به صورت داینامیک را با ASP.NET طراحی کنم(منظور از ایجاد Slideshow به صورت داینامیک این است که عکس‌ها را به صورت داینامیک از DB بخواند).

اولین گام این است که Plugin مورد نظر را دریافت کنید که من از پلاگین Orbit استفاده کرده ام

ابتدا یک DataBase با نام DynamicSlideShow ایجاد و یک جدول با ساختار زیر با نام Picturesدرون آن ایجاد می‌کنیم

گام بعدی ایجاد یک پروژه Asp.Net با زبان C# و اضافه کردن فایلهای پلاگین به پروژه و ایجاد یک Handler برای بازیابی داده‌ها از DB است. ساختار   Solutionما باید به صورت زیر باشد

برای اینکه بتوانیم با DB ارتباط برقرار کنیم از EF استفاده می‌کنیم به همین منظور ابتدا یک Model  به نام DynamicSlideShowModel ایجاد می‌کنیم

در گام بعد بر روی GenerateFromDatabase  انتخاب کرده و بر روی دکمه Next کلیک می‌کنیم و در مراحل بعد ابتدا DB مربوط به مثال (DynamicSlideShow) و جدول آن را انتخاب می‌کنیم . حال یک Model به درون پوشه App_Code  اضافه شده است  

در ادامه برای واکشی رکوردهای موجود در جدول  Pictures  کدهای زیر را درون Handler می‌نویسیم

  var ctx = new DynamicSlideShowEntities();
  var list = ctx.Pictures.ToList();
  string str = JsonConvert.SerializeObject(list);
  context.Response.Write(str);

  

در این کدها تنها نکته‌ای که احتیاج به توضیح دارد این است که ابتدا یک لیست از رکوردها را از جدول Pictures واکشی می‌کنیم و برای پاس دادن به کلاینت ما آن‌ها را به فرمت Json تبدیل کرده ایم که برای تبدبل از کنابخانه آماده Newtonsoft.Json.dll استفاده کرده ایم 

حال باید با استفاده از jQuery کدهای درون Handler را اجرا کنیم؛ برای همین منظور یک صفحه با نام default.aspx ایجاد کرده و کدهای زیر را درون آن می‌نویسیم

<head runat="server">
    <title>Dynamic SlideShow</title>
    <link href="CSS/orbit-1.2.3.css" rel="stylesheet" type="text/css" />
    <script src="Script/jquery-1.7.1.min.js" type="text/javascript"></script>
    <script src="Script/jquery.orbit-1.2.3.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            $.ajax({
                url: "Handler.ashx",
                contentType: "application/json; charset=utf-8",
                success: function (data) {
                    $.each(data, function (i, b) {
                        var str = '<img src="' + b.PicturePath + '" alt="' + b.PictureText + '"/>';
                        $("#featured").append(str);
                    });
                    $('#featured').orbit();
                },
                dataType: "json"

            });
            });
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div id="featured">
    </div>
    </form>
</body>

در اینجا ابتدا با استفاده از متد ajax کتابخانه jQuery کدهای درون Handler را اجرا کرده و به ازای هر المان موجود در جدول یک تگ img به صفحه اضافه می‌کنیم.

نظرات مطالب
لیست تازه‌های IIS 7.5
IIS یک کامپوننت ویندوز است و جدا از ویندوز ارائه نمی‌شود.
نظرات مطالب
هزینه استفاده از دات نت فریم ورک چقدر است؟
علاوه بر مطالبی که دوستان در مورد اشتراک MSDN و فواید آن گفتند ... برنامه‌ی دیگری به نام bizspark از طرف مایکروسافت برای شرکت‌های تازه تاسیس و کمک به آن‌ها وجود دارد. به این ترتیب به مدت سه سال مشترک MSDN خواهید شد و تنها 100 دلار در پایان سه سال باید پرداخت کنید. در طی این مدت دسترسی قانونی به تمام محصولات مهم مایکروسافت را دارید (VS2010, Office2010, MS Axapta, SQL SERVER, SERVER 2008, Windows 7-8 و ...)
http://www.microsoft.com/bizspark/