"architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { //"outputPath": "dist/ngsrc", //"index": "src/index.html", //"main": "src/main.ts", "outputPath": "../../wwwroot/ngsrc", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", "aot": false, "assets": [ "src/favicon.ico", "src/assets" ], "styles": [ "src/styles.css" ], "scripts": [] },
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'ngsrc'; }
//_EntityFormLayout.cshtml @inherits EntityFormRazorPage<dynamic> @{ Layout = null; } <div class="modal-header"> <h4 class="modal-title" asp-if="IsNew">Create New @EntityDisplayName</h4> <h4 class="modal-title" asp-if="!IsNew">Edit @EntityDisplayName</h4> <button type="button" class="close" data-dismiss="modal">×</button> </div> <form asp-action="@(IsNew ? CreateActionName : EditActionName)" asp-modal-form="@FormId"> <div class="modal-body"> <input type="hidden" name="continue-editing" value="true" asp-permission="@EditPermission"/> <input asp-for="@Version" type="hidden"/> <input asp-for="@Id" type="hidden"/> @RenderBody() </div> <div class="modal-footer"> <a class="btn btn-light btn-circle" asp-modal-delete-link asp-model-id="@Id" asp-modal-toggle="false" asp-action="@DeleteActionName" asp-if="!IsNew" asp-permission="@DeletePermission" title="Delete Role"> <i class="fa fa-trash text-danger"></i> </a> <a class="btn btn-light btn-circle" title="Refresh Role" asp-if="!IsNew" asp-modal-link asp-modal-toggle="false" asp-action="@EditActionName" asp-route-id="@Id"> <i class="fa fa-repeat"></i> </a> <a class="btn btn-light btn-circle mr-auto" title="New Role" asp-modal-link asp-modal-toggle="false" asp-permission="@CreatePermission" asp-action="@CreateActionName"> <i class="fa fa-plus"></i> </a> <button type="button" class="btn btn-light" data-dismiss="modal"> <i class="fa fa-ban"></i> Cancel </button> <button type="submit" class="btn btn-outline-primary"> <i class="fa fa-save"></i> Save Changes </button> </div> </form>
public abstract class EntityFormRazorPage<T> : RazorPage<T> { protected string EntityName { get => ViewData[nameof(EntityName)].ToString(); set => ViewData[nameof(EntityName)] = value; } protected string EntityDisplayName { get => ViewData[nameof(EntityDisplayName)].ToString(); set => ViewData[nameof(EntityDisplayName)] = value; } protected string DeletePermission { get => ViewData[nameof(DeletePermission)].ToString(); set => ViewData[nameof(DeletePermission)] = value; } protected string CreatePermission { get => ViewData[nameof(CreatePermission)].ToString(); set => ViewData[nameof(CreatePermission)] = value; } protected string EditPermission { get => ViewData[nameof(EditPermission)].ToString(); set => ViewData[nameof(EditPermission)] = value; } protected string CreateActionName { get => ViewData.TryGetValue(nameof(CreateActionName), out var value) ? value.ToString() : "Create"; set => ViewData[nameof(CreateActionName)] = value; } protected string EditActionName { get => ViewData.TryGetValue(nameof(EditActionName), out var value) ? value.ToString() : "Edit"; set => ViewData[nameof(EditActionName)] = value; } protected string DeleteActionName { get => ViewData.TryGetValue(nameof(DeleteActionName), out var value) ? value.ToString() : "Delete"; set => ViewData[nameof(DeleteActionName)] = value; } protected string FormId => $"{EntityName}Form"; protected bool IsNew => (Model as dynamic).IsNew(); protected string Id => (Model as dynamic).Id.ToString(CultureInfo.InvariantCulture); protected byte[] Version => (Model as dynamic).Version; }
//_BlogPartial.cshtml @inherits EntityFormRazorPage<BlogModel> @{ Layout = "_EntityFormLayout"; EntityName = "Blog"; DeletePermission = PermissionNames.Blogs_Delete; CreatePermission = PermissionNames.Blogs_Create; EditPermission = PermissionNames.Blogs_Edit; EntityDisplayName = "Blog"; }
//_BlogPartial.cshtml @inherits EntityFormRazorPage<BlogModel> @{ Layout = "_EntityFormLayout"; ... } <div class="form-group row"> <div class="col col-md-8"> <label asp-for="Title" class="col-form-label text-md-left"></label> <input asp-for="Title" autocomplete="off" class="form-control"/> <span asp-validation-for="Title" class="text-danger"></span> </div> </div> <div class="form-group row"> <div class="col"> <label asp-for="Url" class="col-form-label text-md-left"></label> <input asp-for="Url" class="form-control" type="url"/> <span asp-validation-for="Url" class="text-danger"></span> </div> </div>
//_BlogPartial.cshtml @inherits EntityFormRazorPage<BlogModel> @{ Layout = "_EntityFormLayout"; EntityName = "Blog"; DeletePermission = PermissionNames.Blogs_Delete; CreatePermission = PermissionNames.Blogs_Create; EditPermission = PermissionNames.Blogs_Edit; EntityDisplayName = "Blog"; } @Html.EditorForModel()
در این مثال به کمک MVC5، یک کپچای ساده و قابل فهم را تولید و استفاده خواهیم کرد. این نوشته بر اساس این مقاله ایجاد شده و جزئیات زیادی برای درک افراد مبتدی به آن افزوده شده است که امیدوارم راهنمای مفیدی برای علاقمندان باشد.
با کلیک راست بر روی پوشه کنترلر، یک کنترلر به منظور ایجاد کپچا بسازید و اکشن متد زیر را در آن کنترلر ایجاد کنید:
public class CaptchaController : Controller { public ActionResult CaptchaImage(string prefix, bool noisy = true) { var rand = new Random((int)DateTime.Now.Ticks); //generate new question int a = rand.Next(10, 99); int b = rand.Next(0, 9); var captcha = string.Format("{0} + {1} = ?", a, b); //store answer Session["Captcha" + prefix] = a + b; //image stream FileContentResult img = null; using (var mem = new MemoryStream()) using (var bmp = new Bitmap(130, 30)) using (var gfx = Graphics.FromImage((Image)bmp)) { gfx.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; gfx.SmoothingMode = SmoothingMode.AntiAlias; gfx.FillRectangle(Brushes.White, new Rectangle(0, 0, bmp.Width, bmp.Height)); //add noise if (noisy) { int i, r, x, y; var pen = new Pen(Color.Yellow); for (i = 1; i < 10; i++) { pen.Color = Color.FromArgb( (rand.Next(0, 255)), (rand.Next(0, 255)), (rand.Next(0, 255))); r = rand.Next(0, (130 / 3)); x = rand.Next(0, 130); y = rand.Next(0, 30); gfx.DrawEllipse(pen, x - r, y - r, r, r); } } //add question gfx.DrawString(captcha, new Font("Tahoma", 15), Brushes.Gray, 2, 3); //render as Jpeg bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg); img = this.File(mem.GetBuffer(), "image/Jpeg"); } return img; }
همانطور که از کد فوق پیداست، دو مقدار a و b، به شکل اتفاقی ایجاد میشوند و حاصل جمع آنها در یک Session نگهداری خواهد شد. سپس تصویری بر اساس تصویر a+b ایجاد میشود (مثل 3+4). این تصویر خروجی این اکشن متد است. به سادگی میتوانید این اکشن را بر اساس خواسته خود اصلاح کنید؛ مثلا به جای حاصل جمع دو عدد، از کاربرد چند حرف یا عدد که بصورت اتفاقی تولید کردهاید، استفاده نمائید.
فرض کنید میخواهیم کپچا را هنگام ثبت نام استفاده کنیم.
در فایل AccountViewModels.cs در پوشه مدلها در کلاس RegisterViewModel خاصیت زیر را اضافه کنید:
[Required(ErrorMessage = "لطفا {0} را وارد کنید")] [Display(Name = "حاصل جمع")] public string Captcha { get; set; }
حالا در پوشه View/Account به فایل Register.Cshtml خاصیت فوق را اضافه کنید:
<div class="form-group"> <input type="button" value="" id="refresh" /> @Html.LabelFor(model => model.Captcha) <img alt="Captcha" id="imgcpatcha" src="@Url.Action("CaptchaImage","Captcha")" style="" /> </div>
وظیفه این بخش، نمایش کپچاست. تگ img دارای آدرسی است که توسط اکشن متدی که در ابتدای این مقاله ایجاد نمودهایم تولید میشود. این آدرس تصویر کپچاست. یک دکمه هم با شناسه refresh برای به روز رسانی مجدد تصویر در نظر گرفتهایم.
حالا کد ایجکسی برای آپدیت کپچا توسط دکمه refresh را به شکل زیر بنویسید (من در پایین ویوی Register، اسکریپت زیر را قرار دادم):
<script type="text/javascript"> $(function () { $('#refresh').click(function () { $.ajax({ url: '@Url.Action("CaptchaImage","Captcha")', type: "GET", data: null }) .done(function (functionResult) { $("#imgcpatcha").attr("src", "/Captcha/CaptchaImage?" + functionResult); }); }); }); </script>
آنچه در url نوشته شده است، شاید اصولیترین شکل فراخوانی یک اکشن متد باشد. این اکشن در ابتدای مقاله تحت کنترلری به نام Captcha معرفی شده بود و خروجی آن آدرس یک فایل تصویری است. نوع ارتباط، Get است و هیچ اطلاعاتی به اکشن متد فرستاده نمیشود، اما اکشن متد ما آدرسی را به ما برمیگرداند که تحت نام FunctionResult آن را دریافت کرده و به کمک کد جی کوئری، مقدارش را در ویژگی src تصویر موجود در صفحه جاری جایگزین میکنیم. دقت کنید که برای دسترسی به تصویر، لازم است جایگزینی آدرس، در ویژگی src به شکل فوق صورت پذیرد.*
تنها کار باقیمانده اضافه کردن کد زیر به ابتدای اکشن متد Register درون کنترلر Account است.
if (Session["Captcha"] == null || Session["Captcha"].ToString() != model.Captcha) { ModelState.AddModelError("Captcha", "مجموع اشتباه است"); }
واضح است که اینکار پیش از شرط if(ModelState.IsValidate) صورت میگیرد و وظیفه شرط فوق، بررسی ِ برابریِ مقدار Session تولید شده در اکشن CaptchaImage (ابتدای این مقاله) با مقدار ورودی کاربر است. (مقداری که از طریق خاصیت تولیدی خودمان به آن دسترسی داریم) . بدیهیاست اگر این دو مقدار نابرابر باشند، یک خطا به ModelState اضافه میشود و شرط ModelState.IsValid که در اولین خط بعد از کد فوق وجود دارد، برقرار نخواهد بود و پیغام خطا در صفحه ثبت نام نمایش داده خواهد شد.
تصویر زیر نمونهی نتیجهای است که حاصل خواهد شد :
* اصلاح : دقت کنید بدون استفاده از ایجکس هم میتوانید تصویر فوق را آپدیت کنید:
$('#refresh').click(function () { var d = new Date(); $("#imgcpatcha").attr("src", "Captcha/CaptchaImage?" + d.getTime()); });
رویداد کلیک را با کد فوق جایگزین کنید؛ دو نکته در اینجا وجود دارد :
یک. استفاده از زمان در انتهای آدرس به خاطر مشکلاتیست که فایرفاکس یا IE با اینگونه آپدیتهای تصویری دارند. این دو مرورگر (بر خلاف کروم) تصاویر را نگهداری میکنند و آپدیت به روش فوق به مشکل برخورد میکند مگر آنکه آدرس را به کمک اضافه کردن زمان آپدیت کنید تا مرورگر متوجه داستان شود
دو. همانطور که میبینید آدرس تصویر در حقیقت خروجی یک اکشن است. پس نیازی نیست هر بار این اکشن را به کمک ایجکس صدا بزنیم و روش فوق در مرورگرهای مختلف جواب خواهد داد.
"wwwroot/lib/jquery/dist/jquery.min.js",
"wwwroot/js/site.min.js" "wwwroot/css/site.min.css"
کتابخانه جایگزین آنرا افزونه XMLWorker معرفی کردهاند که توانایی پردازش CSS و HTML بهتر و کاملتری را نسبت به HTMLWorker ارائه میدهد. این کتابخانه نیز همانند HTMLWorker پشتیبانی توکاری از متون راست به چپ و یونیکد فارسی، ندارد و نیاز است برای نمایش صحیح متون فارسی در آن، نکات خاصی را اعمال نمود که در ادامه بحث آنها را مرور خواهیم کرد.
ابتدا برای دریافت آخرین نگارشهای iTextSharp و افزونه XMLWorker آن به آدرسهای ذیل مراجعه نمائید:
تهیه یک UnicodeFontProvider
Encoding پیش فرض قلمها در XMLWorker مساوی BaseFont.CP1252 است؛ که از حروف یونیکد پشتیبانی نمیکند. برای رفع این نقیصه نیاز است یک منبع تامین قلم سفارشی را برای آن ایجاد نمود:
public class UnicodeFontProvider : FontFactoryImp { static UnicodeFontProvider() { // روش صحیح تعریف فونت var systemRoot = Environment.GetEnvironmentVariable("SystemRoot"); FontFactory.Register(Path.Combine(systemRoot, "fonts\\tahoma.ttf")); // ثبت سایر فونتها در اینجا //FontFactory.Register(Path.Combine(Environment.CurrentDirectory, "fonts\\irsans.ttf")); } public override Font GetFont(string fontname, string encoding, bool embedded, float size, int style, BaseColor color, bool cached) { if (string.IsNullOrWhiteSpace(fontname)) return new Font(Font.FontFamily.UNDEFINED, size, style, color); return FontFactory.GetFont(fontname, BaseFont.IDENTITY_H, BaseFont.EMBEDDED, size, style, color); } }
مابقی مسایل آن خودکار خواهد بود و هر زمانیکه نیاز به قلم خاصی از طرف XMLWorker وجود داشت، به متد GetFont فوق مراجعه کرده و اینبار قلمی با BaseFont.IDENTITY_H را دریافت میکند. IDENTITY_H در استاندارد PDF، جهت مشخص ساختن encoding قلمهایی با پشتیبانی از یونیکد بکار میرود.
تهیه منبع تصاویر
در XMLWorker اگر تصاویر با http شروع نشوند (دریافت تصاویر وب آن خودکار است)، آن تصاویر را از مسیری که توسط پیاده سازی کلاس AbstractImageProvider مشخص خواهد شد، دریافت میکند که نمونهای از پیاده سازی آنرا در ذیل مشاهده میکنید:
public class ImageProvider : AbstractImageProvider { public override string GetImageRootPath() { var path = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures); return path + "\\"; // مهم است که این مسیر به بک اسلش ختم شود تا درست کار کند } }
نحوه تعریف یک فایل CSS خارجی
public static class XMLWorkerUtils { /// <summary> /// نحوه تعریف یک فایل سی اس اس خارجی /// </summary> public static ICssFile GetCssFile(string filePath) { using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { return XMLWorkerHelper.GetCSS(stream); } } }
تبدیل المانهای HTML پردازش شده به یک لیست PDF ایی
تهیه مقدمات فارسی سازی و نمایش راست به چپ اطلاعات در کتابخانه XMLWorker از اینجا شروع میشود. در حالت پیش فرض کار آن، المانهای HTML به صورت خودکار Parse شده و به صفحه اضافه میشوند. به همین دلیل دیگر فرصت اعمال خواص RTL به المانهای پردازش شده دیگر وجود نخواهد داشت و به صورت توکار نیز این مسایل درنظر گرفته نمیشود. به همین دلیل نیاز است که در حین پردازش المانهای HTML و تبدیل آنها به معادل المانهای PDF، بتوان آنها را جمع آوری کرد که نحوه انجام آنرا با پیاده سازی اینترفیس IElementHandler در ذیل مشاهده میکنید:
/// <summary> /// معادل پی دی افی المانهای اچ تی ام ال را جمع آوری میکند /// </summary> public class ElementsCollector : IElementHandler { private readonly Paragraph _paragraph; public ElementsCollector() { _paragraph = new Paragraph { Alignment = Element.ALIGN_LEFT // سبب میشود تا در حالت راست به چپ از سمت راست صفحه شروع شود }; } /// <summary> /// این پاراگراف حاوی کلیه المانهای متن است /// </summary> public Paragraph Paragraph { get { return _paragraph; } } /// <summary> /// بجای اینکه خود کتابخانه اصلی کار افزودن المانها را به صفحات انجام دهد /// قصد داریم آنها را ابتدا جمع آوری کرده و سپس به صورت راست به چپ به صفحات نهایی اضافه کنیم /// </summary> /// <param name="htmlElement"></param> public void Add(IWritable htmlElement) { var writableElement = htmlElement as WritableElement; if (writableElement == null) return; foreach (var element in writableElement.Elements()) { fixNestedTablesRunDirection(element); _paragraph.Add(element); } } /// <summary> /// نیاز است سلولهای جداول تو در توی پی دی اف نیز راست به چپ شوند /// </summary> private void fixNestedTablesRunDirection(IElement element) { var table = element as PdfPTable; if (table == null) return; table.RunDirection = PdfWriter.RUN_DIRECTION_RTL; foreach (var row in table.Rows) { foreach (var cell in row.GetCells()) { cell.RunDirection = PdfWriter.RUN_DIRECTION_RTL; foreach (var item in cell.CompositeElements) { fixNestedTablesRunDirection(item); } } } } }
یک مثال کامل از نحوه کنار هم قرار دادن پیشنیازهای تهیه شده
خوب؛ تا اینجا یک سری پیشنیاز را تهیه کردیم، اما XMLWorker از وجود آنها بیخبر است. برای معرفی آنها باید به نحو ذیل عمل کرد:
using (var pdfDoc = new Document(PageSize.A4)) { var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("test.pdf", FileMode.Create)); pdfWriter.RgbTransparencyBlending = true; pdfDoc.Open(); var html = @"<span style='color:blue; font-family:tahoma;'><b>آزمایش</b></span> کتابخانه <i>iTextSharp</i> <u>جهت بررسی فارسی نویسی</u> <table style='color:blue; font-family:tahoma;' border='1'><tr><td>eeمتن</td></tr></table> <code>This is a code!</code> <br/> <img src='av-13489.jpg' /> "; var cssResolver = new StyleAttrCSSResolver(); // cssResolver.AddCss(XMLWorkerUtils.GetCssFile(@"c:\path\pdf.css")); cssResolver.AddCss(@"code { padding: 2px 4px; color: #d14; white-space: nowrap; background-color: #f7f7f9; border: 1px solid #e1e1e8; }", "utf-8", true); // کار جمع آوری المانهای ترجمه شده به المانهای پی دی اف را انجام میدهد var elementsHandler = new ElementsCollector(); var htmlContext = new HtmlPipelineContext(new CssAppliersImpl(new UnicodeFontProvider())); htmlContext.SetImageProvider(new ImageProvider()); htmlContext.CharSet(Encoding.UTF8); htmlContext.SetAcceptUnknown(true).AutoBookmark(true).SetTagFactory(Tags.GetHtmlTagProcessorFactory()); var pipeline = new CssResolverPipeline(cssResolver, new HtmlPipeline(htmlContext, new ElementHandlerPipeline(elementsHandler, null))); var worker = new XMLWorker(pipeline, parseHtml: true); var parser = new XMLParser(); parser.AddListener(worker); parser.Parse(new StringReader(html)); // با هندلر سفارشی که تهیه کردیم تمام المانهای اچ تی ام ال به المانهای پی دی اف تبدیل شدند // الان تنها کافی کافی است تا اینها را در یک جدول راست به چپ محصور کنیم تا درست نمایش داده شوند var mainTable = new PdfPTable(1) { WidthPercentage = 100, RunDirection = PdfWriter.RUN_DIRECTION_RTL }; var cell = new PdfPCell { Border = 0, RunDirection = PdfWriter.RUN_DIRECTION_RTL, HorizontalAlignment = Element.ALIGN_LEFT }; cell.AddElement(elementsHandler.Paragraph); mainTable.AddCell(cell); pdfDoc.Add(mainTable); } Process.Start("test.pdf");
UnicodeFontProvider باید به HtmlPipelineContext شناسانده شود.
ImageProvider توسط متد SetImageProvider به HtmlPipelineContext معرفی میشود.
ElementsCollector سفارشی ما در قسمت CssResolverPipeline باید به سیستم تزریق شود.
پس از آن XMLWorker را وادار میکنیم تا HTML را Parse کرده و معادل المانهای PDF ایی آنرا تهیه کند؛ اما آنها را به صورت خودکار به صفحات فایل PDF نهایی اضافه نکند. در این بین ElementsCollector ما این المانها را جمع آوری کرده و در نهایت، پاراگراف کلی حاصل از آنرا به یک جدول با RUN_DIRECTION_RTL اضافه میکنیم. حاصل آن نمایش صحیح متون فارسی است.
کدهای مثال فوق را از آدرس ذیل نیز میتوانید دریافت کنید:
XMLWorkerRTLsample.cs
به روز رسانی
کلیه نکات مطلب فوق را به همراه بهبودهای مطرح شده در نظرات آن، در پروژهی ذیل میتوانید به صورت یکجا دریافت و بررسی کنید:
XMLWorkerRTLsample.zip
C:\Windows\System32\inetsrv\config\applicationHost.config
جدول خصوصیت ها
خصوصیت | توضیح |
customLogPluginClsid | یک پارامتر رشتهای اختیاری که در آن، آی دی کلاس یا کلاسهایی نوشته میشود که برای custom logging نوشته شدهاند و این گزینه ترتیب اجرای آنها را تعیین میکند. |
directory | اختیاری است. محل ذخیرهی لاگ فایلها را مشخص میکند و در صورتیکه ذکر نشود، همان مسیر پیش فرض است. |
enabled | اختیاری است. فعال بودن سیستم لاگ برای آن سایت را مشخص میکند. مقدار پیش فرض آن true است. |
flushByEntryCountW3CLog | این مقدار مشخص میکند چند رخداد باید اتفاق بیفتد تا عمل ذخیره سازی لاگ صورت گیرد. اگر بعد از هر رخداد عمل ثبت لاگ انجام شود، سرعت ثبت لاگها بالا میرود؛ ولی باعث استفادهی مداوم از منابع و همچنین درخواست ثبت اطلاعات را روی دیسک خواهد داد و تاوان آن با زیاد شدن عملیات روی دیسک، پرداخته خواهد شد. ولی در حالتیکه چند رخداد را نگهداری سپس دستهای ثبت کند، باعث افزایش کارآیی و راندمان سرور خواهد شد. در صورتیکه سرور به مشکلات لحظهای برخورد میکند مقدار آن را کاهش دهید. مقدار پیش فرض 0 است. یعنی اینکه ثبت، بعد از 64000 لاگ خواهد بود. |
localTimeRollover | نحوهی نامگذاری فایلهای لاگ را مشخص میکند که مقدار بولین گرفته و اختیاری است. به طور پیش فرض مقدار false دارد. |
logExtFileFlags | این گزینه در حالتی به کارتان میآید که فرمت W3C را برای ثبت لاگها انتخاب کرده باشید و در اینجا مشخص میکنید که چه فیلدهایی باید در لاگ باشند و اگر بیش از یکی بود میتوان با ، (کاما) از هم جدایشان کرد. |
logFormat | نوع فرمت ذخیره سازی لاگها |
logSiteId | اختیاری است و مقدار پیش فرض آن true است. بدین معنا که کد یا شمارهی سایت هم در لاگ خواهد بود و این در حالتی است که گزارش در سطح سرور باشد. در غیر این صورت اگر هر سایت، جداگانه لاگی برای خود داشته باشد، ذکر نمیگردد. |
logTargetW3C | اختیاری است و و مقدار file و *ETW را میگیرد که به طور پیش فرض روی File تنظیم است. در این حالت فایل لاگها در یک فایل متنی توسط http.sys ذخیره میشود. ولی موقعیکه از ETW استفاده میشود، http.sys با استفاده از iislogprovider دادهها را به سمت ETW ارسال میکند که منجر به اجرای سرویس Logsvc شده که از دادهها کوئری گرفته و آنها را مستقیما از پروسههای کارگر جمع آوری و به سمت فایل لاگ ارسال میکند. همچنین انتخاب این دو گزینه نیز ممکن است. |
maxLogLineLength | حداکثر تعداد خطی که یک لاگ میتواند داشته باشد تا اینکه بتوانید در مصرف دیسک سخت صرفه جویی کنید و بیشتر کاربرد آن برای لاگهای کاستوم است. این عدد باید از نوع Uint باشد و اختیاری است و از 2 تا 65536 مقدار میپذیرد که مقدار پیش فرض آن 65536 میباشد. |
period | همان مبحث زمان بندی در مورد ایجاد فایلهای لاگ است که در مقالهی پیشین برسی کردیم و مقادیر Dialy,Hourly,monthlyو weekly را میپذیرد. همچنین maxsize هم هست؛ موقعی که لاگ به نهایت حجمی که برای آن تعیین کردیم میرسد. |
truncateSize | اختیاری است و مقدار آن از نوع int64 است. حداکثر حجم یک فایل لاگ را مشخص میکند تا اگر period روی maxsize تنظیم شده بود، حداکثر حجم را میتوان از اینجا تعیین نمود. در مقاله پیشین در این باره صحبت کردیم؛ حداقل عدد برای آن 1,048,576 است و اگر کمتر از آن بنویسید، سیستم همین عدد 1,048,576 را در نظر خواهد گرفت. مقدار پیش فرض آن 20971520 می باشد. |
<system.applicationHost> <sites> <siteDefaults> <logFile logFormat="W3C" directory="%SystemDrive%\inetpub\logs\LogFiles" enabled="true"> <customFields> <clear/> <add logFieldName="ContosoField" sourceName="ContosoSource" sourceType="ServerVariable" /> </customFields> </logFile> </siteDefaults> </sites> </system.applicationHost>
تغییر تنظمیات لاگ با Appcmd
appcmd.exe set config -section:system.applicationHost/sites /siteDefaults.logFile.enabled:"True" /commit:apphost appcmd.exe set config -section:system.applicationHost/sites /siteDefaults.logFile.logFormat:"W3C" /commit:apphost appcmd.exe set config -section:system.applicationHost/sites /siteDefaults.logFile.directory:"%SystemDrive%\inetpub\logs\LogFiles" /commit:apphost
تنظمیات تگ لاگ با برنامه نویسی و اسکریپت نویسی
using System; using System.Text; using Microsoft.Web.Administration; internal static class Sample { private static void Main() { using (ServerManager serverManager = new ServerManager()) { Configuration config = serverManager.GetApplicationHostConfiguration(); ConfigurationSection sitesSection = config.GetSection("system.applicationHost/sites"); ConfigurationElement siteDefaultsElement = sitesSection.GetChildElement("siteDefaults"); ConfigurationElement logFileElement = siteDefaultsElement.GetChildElement("logFile"); logFileElement["logFormat"] = @"W3C"; logFileElement["directory"] = @"%SystemDrive%\inetpub\logs\LogFiles"; logFileElement["enabled"] = true; serverManager.CommitChanges(); } } }
با استفاده از اسکریپت نویسی توسط جاوااسکریپت و وی بی اسکریپت هم نیز این امکان مهیاست:
var adminManager = new ActiveXObject('Microsoft.ApplicationHost.WritableAdminManager'); adminManager.CommitPath = "MACHINE/WEBROOT/APPHOST"; var sitesSection = adminManager.GetAdminSection("system.applicationHost/sites", "MACHINE/WEBROOT/APPHOST"); var siteDefaultsElement = sitesSection.ChildElements.Item("siteDefaults"); var logFileElement = siteDefaultsElement.ChildElements.Item("logFile"); logFileElement.Properties.Item("logFormat").Value = "W3C"; logFileElement.Properties.Item("directory").Value = "%SystemDrive%\\inetpub\\logs\\LogFiles"; logFileElement.Properties.Item("enabled").Value = true; adminManager.CommitChanges();
FTP Logging
IIS را باز کنید و در لیست درختی، سرور را انتخاب کنید. در قسمت FTP میتوانید گزینهی Ftp logging را ببینید. تنظیمات این قسمت هم دقیقا همانند قسمت logging میباشد و همان موارد برای آن هم صدق میکند.
بررسی تگ آن در applicationhost
تگ این نوع لاگ در فایل applicationhost در زیر مجموعهی تگ <site> به شکل زیر نوشته میشود:
<system.ftpServer> <log centralLogFileMode="Central"> <centralLogFile enabled="true" /> </log> </system.ftpServer>
گزینه centralLogFileMode دو مقدار central و site را میپذیرد. اگر گزینهی central انتخاب شود، یعنی همهی لاگها را داخل یک فایل در سطح سرور ثبت کن ولی اگر گزینهی site انتخاب شده باشد، لاگ هر سایت در یک فایل ثبت خواهد شد.
گزینهی logInUTF8 یک خصوصیت اختیاری است که مقدار پیش فرض آن true میباشد. در این حالت باید تمامی رشتهها به انکدینگ UTF-8 تبدیل شوند.
همانطور که میبینید تگ log در بالا یک تگ فرزند هم به اسم centralLogFile دارد که همان خصوصیات جدول بالا در آن مهیاست.
دسترسی به تنظیمات این قسمت توسط دستور Appcmd:
appcmd.exe set config -section:system.ftpServer/log /centralLogFileMode:"Central" /commit:apphost appcmd.exe set config -section:system.ftpServer/log /centralLogFile.enabled:"True" /commit:apphost
دسترسی به تنظیمات این قسمت توسط دات نت:
using System; using System.Text; using Microsoft.Web.Administration; internal static class Sample { private static void Main() { using (ServerManager serverManager = new ServerManager()) { Configuration config = serverManager.GetApplicationHostConfiguration(); ConfigurationSection logSection = config.GetSection("system.ftpServer/log"); logSection["centralLogFileMode"] = @"Central"; ConfigurationElement centralLogFileElement = logSection.GetChildElement("centralLogFile"); centralLogFileElement["enabled"] = true; serverManager.CommitChanges(); } } }
دسترسی به تنظیمات این قسمت توسط Javascript:
var adminManager = new ActiveXObject('Microsoft.ApplicationHost.WritableAdminManager'); adminManager.CommitPath = "MACHINE/WEBROOT/APPHOST"; var logSection = adminManager.GetAdminSection("system.ftpServer/log", "MACHINE/WEBROOT/APPHOST"); logSection.Properties.Item("centralLogFileMode").Value = "Central"; var centralLogFileElement = logSection.ChildElements.Item("centralLogFile"); centralLogFileElement.Properties.Item("enabled").Value = true; adminManager.CommitChanges();
بر این اساس، کلاسهای مدل دومین مساله به صورت زیر خواهند بود:
public class Project { public int Id { set; get; } public string Name { set; get; } public virtual ICollection<ProjectIssue> ProjectIssues { set; get; } } public class ProjectIssue { public int Id { set; get; } public string Body { set; get; } [ForeignKey("ProjectStatusId")] public virtual ProjectIssueStatus ProjectIssueStatus { set; get; } public int ProjectStatusId { set; get; } [ForeignKey("ProjectId")] public virtual Project Project { set; get; } public int ProjectId { set; get; } } public class ProjectIssueStatus { public int Id { set; get; } public string Name { set; get; } public virtual ICollection<ProjectIssue> ProjectIssues { set; get; } }
اگر EF Code first را وادار به تهیه جداول و روابط معادل کلاسهای فوق کنیم:
public class MyContext : DbContext { public DbSet<ProjectIssueStatus> ProjectStatus { get; set; } public DbSet<ProjectIssue> ProjectIssues { get; set; } public DbSet<Project> Projects { get; set; } } public class Configuration : DbMigrationsConfiguration<MyContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { var project1 = new Project { Name = "پروژه جدید" }; context.Projects.Add(project1); var stat1 = new ProjectIssueStatus { Name = "درحال انجام" }; var stat2 = new ProjectIssueStatus { Name = "انجام شد" }; context.ProjectStatus.Add(stat1); context.ProjectStatus.Add(stat2); var issue1 = new ProjectIssue { Body = "تغییر قلم گزارش", ProjectIssueStatus = stat1, Project = project1 }; var issue2 = new ProjectIssue { Body = "تغییر لوگوی گزارش", ProjectIssueStatus = stat1, Project = project1 }; context.ProjectIssues.Add(issue1); context.ProjectIssues.Add(issue2); base.Seed(context); } }
سابقا برای یافتن تعداد متناظر با هر IssueStatus خیلی سریع میشد چنین کوئری را نوشت:
اما اکنون معادل آن با EF Code first چیست؟
public static class Test { public static void RunTests() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>()); using (var ctx = new MyContext()) { var projectId = 1; var list = ctx.ProjectStatus.Select(x => new { Id = x.Id, Name = x.Name, Count = x.ProjectIssues.Count(p => p.ProjectId == projectId) }).ToList(); foreach (var item in list) Console.WriteLine("{0}:{1}",item.Name, item.Count); } } }
SELECT [Project1].[Id] AS [Id], [Project1].[Name] AS [Name], [Project1].[C1] AS [C1] FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], ( SELECT COUNT(1) AS [A1] FROM [dbo].[ProjectIssues] AS [Extent2] WHERE ([Extent1].[Id] = [Extent2].[ProjectStatusId]) AND ([Extent2].[ProjectId] = 1 /*@p__linq__0*/) ) AS [C1] FROM [dbo].[ProjectIssueStatus] AS [Extent1] ) AS [Project1]
اگر مطلب "ذخیره سازی SQL تولیدی در NH3" را دنبال کرده باشید که یک مثال عملی از "NHibernate 3.0 و عدم وابستگی مستقیم به Log4Net" بود، خروجی حاصل از آن به صورت زیر است:
---+ 12/29/2010 05:35:59.75 +---
SQL ...
---+ 12/29/2010 05:35:59.75 +---
SQL ...
خوشبختانه در دات نت فریم ورک میتوان با بررسی Stack trace ، رد کاملی را از فراخوانهای متدها یافت:
StackTrace stackTrace = new StackTrace();
StackFrame stackFrame = stackTrace.GetFrame(1);
MethodBase methodBase = stackFrame.GetMethod();
Type callingType=methodBase.DeclaringType;
بر این اساس سورس مثال قبل را جهت درج اطلاعات فراخوانهای متدها تکمیل کردهام، که از این آدرس قابل دریافت است.
اکنون اگر از این ماژول جدید استفاده کنیم، خروجی نمونهی آن به صورت زیر خواهد بود:
---+ 01/02/2011 02:19:24.98 +---
-- Void ASP.feedback_aspx.ProcessRequest(System.Web.HttpContext) [File=App_Web_4nvdip40.5.cs, Line=0]
--- Void Prog.Web.UserCtrls.FeedbacksList.Page_Load(System.Object, System.EventArgs) [File=FeedbacksList.ascx.cs, Line=23]
---- Void Prog.Web.UserCtrls.FeedbacksList.BindTo() [File=FeedbacksList.ascx.cs, Line=43]
----- System.Collections.Generic.IList`1[Feedback] Prog.GetAllUserFeedbacks(Int32) [File=FeedbackWebRepository.cs, Line=66]
SELECT ...
@p0 = 3 [Type: Int32 (0)]
اینطوری حداقل میتوان دریافت که SQL تولیدی دقیقا به کجا بر میگردد و چه متدی سبب صدور آن شده است.
ملاحظات:
این ماژول تنها در صورت وجود فایل pdb معتبر کنار اسمبلیهای شما این خروجی مفصل را تولید خواهد کرد. در غیر اینصورت از آن صرفنظر میکند. (برای مثال نام فایل سورس فراخوان، شمارهی سطر فراخوان، حتی محل قرارگیری آن فایل بر روی کامپیوتر شما در فایلهای pdb ثبت میگردند؛ به همین جهت توصیه اکید حین ارائهی نهایی برنامه، حذف این نوع فایلها است)
نحوه ارتقاء برنامههای موجود MVC3 به MVC4
<script src="/Asset/Scripts/js?v="></script> <script src="/Asset/Scripts/JQuery/Zebra/js?v="></script> <script src="/Asset/Scripts/Angular/Asset/js?v="></script>