با تشکر؛ این کد فقط توی صفحات html خوب کار میکنه و توی صفحات asp کار نمیکنه. برای اینکه بتونم توی صفحه asp هم ازش استفاده کنم باید چیکار کرد؟
برای تهیه تصاویر سایتهای معرفی شده در قسمت اشتراکهای سایت، پیشتر از کنترل WebBrowser دات نت که در پشت صحنه از امکانات IE کمک میگیرد، استفاده میکردم. بسیار ناپایدار است؛ به روز رسانی مشکلی داشته و وابسته است به سیستم عامل جاری سیستم. برای مثال مرتبا برای تهیه تصاویر بند انگشتی (Thumbnails) سایتهای تهیه شده با بوت استرپ کرش میکرد و این کرش چون از نوع unmaged code است، عملا پروسه IIS وب سایت را از کار میانداخت و در این حالت سایت تا ریاستارت بعدی IIS در دسترس نبود. برای حل این مشکل و بهبود کیفیت تصاویر تهیه شده، از پروژه Awesomium که در حقیقت مرورگر کروم را جهت استفاده در انواع و اقسام زبانهای برنامه نویسی محصور میکند، کمک گرفته شد؛ که شرح آنرا در ادامه ملاحظه خواهید کرد.
دریافت و نصب Awesomium
پروژه Awesomium دارای یک SDK است که از اینجا قابل دریافت میباشد. بعد از نصب آن در مسیر Awesomium SDK\1.7.3.0\wrappers\Awesomium.NET\Assemblies\Packed میتوانید محصور کنندهی دات نتی آنرا مشاهده کنید. منظور از Packed در اینجا، استفاده از DLLهای فشرده شدهی native آن است که در مسیر Awesomium SDK\1.7.3.0\build\bin\packed کپی شدهاند. بنابراین برای توزیع این نوع برنامهها نیاز است اسمبلی دات نتی Awesomium.Core.dll به همراه دو فایل بومی icudt.dll و awesomium.dll ارائه شوند.
تهیه تصاویر سایتها به کمک Awesomium.NET
پس از نصب Awesomium اگر به مسیر Documents\Awesomium SDK Samples\1.7.3.0\Awesomium.NET\Samples\Core\CSharp\BasicSample مراجعه کنید، مثالی را در مورد تهیه تصاویر سایتها به کمک Awesomium.NET، مشاهده خواهید کرد. خلاصهی آن چند سطر ذیل است:
کار با ایجاد یک WebSession شروع میشود. سپس با مقدار دهی CustomCSS، اسکرول بار صفحات را جهت تهیه تصاویری بهتر مخفی میکنیم. سپس یک WebView آغاز شده و منبع آن به Url مدنظر تنظیم میشود. در ادامه باید اندکی صبر کنیم تا بارگذاری سایت خاتمه یافته و نهایتا میتوانیم سطح این View را به صورت یک تصویر ذخیره کنیم.
مشکل! این روش در برنامههای ASP.NET کار نمیکند!
مثال همراه آن یک مثال کنسول ویندوزی است و به خوبی کار میکند؛ اما در برنامههای وب پس از چند روز سعی و خطا مشخص شد که:
الف) WebCore.Shutdown فقط باید در پایان کار یک برنامه فراخوانی شود. یعنی اصلا نیازی نیست تا در برنامههای وب فراخوانی شود.
ب) Awesomium فقط در یک ترد کار میکند. به این معنا که اگر کدهای فوق را در یک صفحهی وب فراخوانی کنید، بار اول کار خواهد کرد. بار دوم برنامه کرش میکند؛ با این پیغام خطا:
چون هر صفحهی وب در یک ترد مجزا اجرا میشود، عملا استفادهی مستقیم از Awesomium در آن ممکن نیست.
خطای فوق هم از آن نوع خطاهایی است که پروسهی IIS را درجا خاموش میکند.
استفاده از Awesomium در یک ترد پس زمینه
راه حلی که نهایتا پاسخ داد و به خوبی و پایدار کار میکند، شامل ایجاد یک ترد مجزای Awesomium در زمان آغاز برنامهی وب و زنده نگه داشتن آن تا زمان پایان کار برنامه است.
در اینجا اگر در برنامههای وب فرم، از طریق HttpContext.Current.Items.Add و یا در برنامههای MVC به کمک this.HttpContext.Items.Add یک آیتم جدید، با کلید Constants.AwesomiumRequest و از نوع کلاس AwesomiumRequest دریافت گردد، مقدار آن به یک ConcurrentQueue اضافه خواهد شد. این صف در یک ترد مجزا مدام در حال بررسی است. اگر مقداری به آن اضافه شدهاست، از صف خارج شده و پردازش خواهد شد.
نمونهی استفاده از آن، در سمت یک برنامهی وب نیز به صورت زیر است. ابتدا ماژول تهیه شده باید در برنامه ثبت شود:
سپس باید تنها مدیریت HttpContext.Current.Items در سمت برنامه صورت گیرد:
در اینجا کاربر درخواست خود را در صف قرار میدهد. پس از مدتی کار آن در WorkerThread ماژول تهیه شده انجام گردیده و تصویر نهایی تهیه میشود.
Url، آدرس وب سایتی است که میخواهید تصویر آن تهیه شود. SavePath مسیر کامل فایل jpg نهایی است که قرار است دریافت و ذخیره گردد. TempDir محل ذخیره سازی فایلهای موقتی Awesomium است. Awesomium یک سری کوکی، تصاویر و فایلهای هر سایت را به این ترتیب کش کرده و در دفعات بعدی سریعتر عمل میکند.
پروژهی کامل آنرا از اینجا میتوانید دریافت کنید:
AwesomiumWebApplication_V1.0.zip
دریافت و نصب Awesomium
پروژه Awesomium دارای یک SDK است که از اینجا قابل دریافت میباشد. بعد از نصب آن در مسیر Awesomium SDK\1.7.3.0\wrappers\Awesomium.NET\Assemblies\Packed میتوانید محصور کنندهی دات نتی آنرا مشاهده کنید. منظور از Packed در اینجا، استفاده از DLLهای فشرده شدهی native آن است که در مسیر Awesomium SDK\1.7.3.0\build\bin\packed کپی شدهاند. بنابراین برای توزیع این نوع برنامهها نیاز است اسمبلی دات نتی Awesomium.Core.dll به همراه دو فایل بومی icudt.dll و awesomium.dll ارائه شوند.
تهیه تصاویر سایتها به کمک Awesomium.NET
پس از نصب Awesomium اگر به مسیر Documents\Awesomium SDK Samples\1.7.3.0\Awesomium.NET\Samples\Core\CSharp\BasicSample مراجعه کنید، مثالی را در مورد تهیه تصاویر سایتها به کمک Awesomium.NET، مشاهده خواهید کرد. خلاصهی آن چند سطر ذیل است:
try { using (WebSession mywebsession = WebCore.CreateWebSession( new WebPreferences() { CustomCSS = "::-webkit-scrollbar { visibility: hidden; }" })) { using (var view = WebCore.CreateWebView(1240, 1000, mywebsession)) { view.Source = new Uri("https://site.com/"); bool finishedLoading = false; view.LoadingFrameComplete += (s, e) => { if (e.IsMainFrame) finishedLoading = true; }; while (!finishedLoading) { Thread.Sleep(100); WebCore.Update(); } using (var surface = (BitmapSurface)view.Surface) { surface.SaveToJPEG("result.jpg"); } } } } finally { WebCore.Shutdown(); }
مشکل! این روش در برنامههای ASP.NET کار نمیکند!
مثال همراه آن یک مثال کنسول ویندوزی است و به خوبی کار میکند؛ اما در برنامههای وب پس از چند روز سعی و خطا مشخص شد که:
الف) WebCore.Shutdown فقط باید در پایان کار یک برنامه فراخوانی شود. یعنی اصلا نیازی نیست تا در برنامههای وب فراخوانی شود.
System.InvalidOperationException: You are attempting to re-initialize the WebCore. The WebCore must only be initialized once per process and must be shut down only when the process exits.
System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt. at Awesomium.Core.NativeMethods.WebCore_CreateWebView_1(HandleRef jarg1, Int32 jarg2, Int32 jarg3, HandleRef jarg4)
خطای فوق هم از آن نوع خطاهایی است که پروسهی IIS را درجا خاموش میکند.
استفاده از Awesomium در یک ترد پس زمینه
راه حلی که نهایتا پاسخ داد و به خوبی و پایدار کار میکند، شامل ایجاد یک ترد مجزای Awesomium در زمان آغاز برنامهی وب و زنده نگه داشتن آن تا زمان پایان کار برنامه است.
using System; using System.Collections.Concurrent; using System.IO; using System.Threading; using System.Web; using Awesomium.Core; namespace AwesomiumWebModule { public class AwesomiumModule : IHttpModule { private static readonly Thread WorkerThread = new Thread(awesomiumWorker); private static readonly ConcurrentQueue<AwesomiumRequest> TaskQueue = new ConcurrentQueue<AwesomiumRequest>(); private static bool _isRunning = true; static AwesomiumModule() { WorkerThread.Start(); } private static void awesomiumWorker() { while (_isRunning) { if (TaskQueue.Count != 0) { AwesomiumRequest outRequest; if (TaskQueue.TryDequeue(out outRequest)) { var img = AwesomiumThumbnail.FetchWebPageThumbnail(outRequest); File.WriteAllBytes(outRequest.SavePath, img); Thread.Sleep(500); } } Thread.Sleep(5); } } public void Dispose() { _isRunning = false; WebCore.Shutdown(); } public void Init(HttpApplication context) { context.EndRequest += endRequest; } static void endRequest(object sender, EventArgs e) { var url = HttpContext.Current.Items[Constants.AwesomiumRequest] as AwesomiumRequest; if (url!=null) { TaskQueue.Enqueue(url); } } } }
نمونهی استفاده از آن، در سمت یک برنامهی وب نیز به صورت زیر است. ابتدا ماژول تهیه شده باید در برنامه ثبت شود:
<?xml version="1.0"?> <configuration> <system.web> <compilation debug="true" targetFramework="4.0" /> <httpModules> <add name="AwesomiumWebModule" type="AwesomiumWebModule.AwesomiumModule"/> </httpModules> </system.web> <system.webServer> <validation validateIntegratedModeConfiguration="false"/> <modules> <add name="AwesomiumWebModule" type="AwesomiumWebModule.AwesomiumModule"/> </modules> </system.webServer> </configuration>
protected void btnStart_Click(object sender, EventArgs e) { var host = new Uri(txtUrl.Text).Host; HttpContext.Current.Items.Add(Constants.AwesomiumRequest, new AwesomiumRequest { Url = txtUrl.Text, SavePath = Path.Combine(HttpRuntime.AppDomainAppPath, "App_Data\\Thumbnails\\" + host + ".jpg"), TempDir = Path.Combine(HttpRuntime.AppDomainAppPath, "App_Data\\Temp") }); lblInfo.Text = "Please wait. Your request will be served shortly."; }
Url، آدرس وب سایتی است که میخواهید تصویر آن تهیه شود. SavePath مسیر کامل فایل jpg نهایی است که قرار است دریافت و ذخیره گردد. TempDir محل ذخیره سازی فایلهای موقتی Awesomium است. Awesomium یک سری کوکی، تصاویر و فایلهای هر سایت را به این ترتیب کش کرده و در دفعات بعدی سریعتر عمل میکند.
پروژهی کامل آنرا از اینجا میتوانید دریافت کنید:
AwesomiumWebApplication_V1.0.zip
پیشتر مطلب «تهیه پردازندههای سفارشی برای HTMLWorker کتابخانه iTextSharp» را در این سایت مطالعه کردهاید. از آنجائیکه افزونه HTMLWorker منسوخ شده است و دیگر پشتیبانی نخواهد شد، باید کدهای فعلی را به افزونه XMLWorker منتقل کرد. مقدمهای را در این زمینه در مطلب «تبدیل HTML فارسی به PDF با استفاده از افزونهی XMLWorker کتابخانهی iTextSharp» میتوانید مطالعه نمائید.
در ادامه قصد داریم همان امکان پشتیبانی از تصاویر base64 مدفون شده در صفحات HTML را به کتابخانه XMLWorker نیز اضافه کنیم.
تهیه پردازندههای سفارشی تگهای HTML جهت افزونه XMLWorker
در اینجا کار با ارث بری از کلاس AbstractTagProcessor شروع میشود. سپس کافی است تا متد End این کلاس پایه را override کرده و تگ سفارشی خود را پردازش کنیم. نمونههایی از این نوع پردازندهها را در پوشه itextsharp.xmlworker\iTextSharp\tool\xml\html سورسهای iTextSharp میتوانید ملاحظه کنید و جهت ایده گرفتن بسیار مناسب میباشند.
یکی از این پردازندههای پیش فرض موجود در افزونه XMLWorker کار پردازش تگهای img را انجام میدهد و در کلاس iTextSharp.tool.xml.html.Image قرار گرفته است. این پردازنده به صورت پیش فرض تصاویر base64 را پردازش نمیکند. برای رفع این مشکل میتوان به نحو ذیل عمل کرد:
با ارث بری از کلاس پردازنده پیش فرض تگهای تصاویر یا iTextSharp.tool.xml.html.Image شروع و سپس متد End آنرا تحریف کردهایم.
در اینجا اگر src یک تگ img با الگوی تصاویر base64 شروع شده باشد، تصویر آن استخراج و تبدیل به وهلهای از تصاویر استاندارد iTextSharp میشود. سپس این تصویر در اختیار htmlPipelineContext قرار داده خواهد شد و یا اگر این تصویر از نوع base64 نباشد، همان متد base.End کلاس پایه، جهت پردازش آن کفایت میکند.
استفاده از یک پردازنده تگ سفارشی HTML افزونه XMLWorker
تا اینجا یک پردازنده جدید تصاویر را ایجاد کردهایم. برای اینکه XMLWorker را از وجود آن مطلع کنیم، نیاز است آنرا به درون htmlPipeline این افزونه تزریق نمائیم که کدهای کامل آنرا در ذیل مشاهده میکنید:
در اینجا ابتدا لیست پردازندههای پیش فرض افزونه XMLWorker را دریافت و سپس پردازنده تگ img آنرا حذف و با نمونه جدید خود جایگزین کردهایم. در ادامه این لیست تغییر یافته به درون HtmlPipelineContext تزریق شدهاست تا بجای DefaultTagProcessorFactory اصلی مورد استفاده قرار گیرد.
در ادامه قصد داریم همان امکان پشتیبانی از تصاویر base64 مدفون شده در صفحات HTML را به کتابخانه XMLWorker نیز اضافه کنیم.
تهیه پردازندههای سفارشی تگهای HTML جهت افزونه XMLWorker
در اینجا کار با ارث بری از کلاس AbstractTagProcessor شروع میشود. سپس کافی است تا متد End این کلاس پایه را override کرده و تگ سفارشی خود را پردازش کنیم. نمونههایی از این نوع پردازندهها را در پوشه itextsharp.xmlworker\iTextSharp\tool\xml\html سورسهای iTextSharp میتوانید ملاحظه کنید و جهت ایده گرفتن بسیار مناسب میباشند.
یکی از این پردازندههای پیش فرض موجود در افزونه XMLWorker کار پردازش تگهای img را انجام میدهد و در کلاس iTextSharp.tool.xml.html.Image قرار گرفته است. این پردازنده به صورت پیش فرض تصاویر base64 را پردازش نمیکند. برای رفع این مشکل میتوان به نحو ذیل عمل کرد:
public class CustomImageTagProcessor : iTextSharp.tool.xml.html.Image { public override IList<IElement> End(IWorkerContext ctx, Tag tag, IList<IElement> currentContent) { IDictionary<string, string> attributes = tag.Attributes; string src; if (!attributes.TryGetValue(HTML.Attribute.SRC, out src)) return new List<IElement>(1); if (string.IsNullOrEmpty(src)) return new List<IElement>(1); if (src.StartsWith("data:image/", StringComparison.InvariantCultureIgnoreCase)) { // data:[<MIME-type>][;charset=<encoding>][;base64],<data> var base64Data = src.Substring(src.IndexOf(",") + 1); var imagedata = Convert.FromBase64String(base64Data); var image = iTextSharp.text.Image.GetInstance(imagedata); var list = new List<IElement>(); var htmlPipelineContext = GetHtmlPipelineContext(ctx); list.Add(GetCssAppliers().Apply(new Chunk((iTextSharp.text.Image)GetCssAppliers().Apply(image, tag, htmlPipelineContext), 0, 0, true), tag, htmlPipelineContext)); return list; } else { return base.End(ctx, tag, currentContent); } } }
در اینجا اگر src یک تگ img با الگوی تصاویر base64 شروع شده باشد، تصویر آن استخراج و تبدیل به وهلهای از تصاویر استاندارد iTextSharp میشود. سپس این تصویر در اختیار htmlPipelineContext قرار داده خواهد شد و یا اگر این تصویر از نوع base64 نباشد، همان متد base.End کلاس پایه، جهت پردازش آن کفایت میکند.
استفاده از یک پردازنده تگ سفارشی HTML افزونه XMLWorker
تا اینجا یک پردازنده جدید تصاویر را ایجاد کردهایم. برای اینکه XMLWorker را از وجود آن مطلع کنیم، نیاز است آنرا به درون htmlPipeline این افزونه تزریق نمائیم که کدهای کامل آنرا در ذیل مشاهده میکنید:
using (var doc = new Document(PageSize.A4)) { var writer = PdfWriter.GetInstance(doc, new FileStream("test.pdf", FileMode.Create)); doc.Open(); var html = @"<img src='data:image/png;base64,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' width='62' height='80' style='float: left; margin-right: 28px;' />"; var tagProcessors = (DefaultTagProcessorFactory)Tags.GetHtmlTagProcessorFactory(); tagProcessors.RemoveProcessor(HTML.Tag.IMG); // remove the default processor tagProcessors.AddProcessor(HTML.Tag.IMG, new CustomImageTagProcessor()); // use our new processor var cssResolver = new StyleAttrCSSResolver(); cssResolver.AddCss(@"code { padding: 2px 4px; }", "utf-8", true); var charset = Encoding.UTF8; var hpc = new HtmlPipelineContext(new CssAppliersImpl(new XMLWorkerFontProvider())); hpc.SetAcceptUnknown(true).AutoBookmark(true).SetTagFactory(tagProcessors); // inject the tagProcessors var htmlPipeline = new HtmlPipeline(hpc, new PdfWriterPipeline(doc, writer)); var pipeline = new CssResolverPipeline(cssResolver, htmlPipeline); var worker = new XMLWorker(pipeline, true); var xmlParser = new XMLParser(true, worker, charset); xmlParser.Parse(new StringReader(html)); } Process.Start("test.pdf");
عموما در اکثر مطالب مقایسهای بین وب فرمها و ASP.NET MVC به جداسازی بهتر منطق کدها از فرمها و قابلیت بهتر تهیه آزمونهای واحد اشاره میشود. در این مطلب از دیدگاهی دیگر به این مساله خواهیم پرداخت؛ از لحاظ فنی و جدای از مسایل یاد شده، چه مزایای دیگری را میتوان با استفاده از ASP.NET MVC نسبت به وب فرمها به دست آورد؟
1- آدرسهای تمیزتر
در ASP.NET MVC به صورت پیش فرض از سیستم Routing موجود در زیر ساخت ASP.NET برای نمایش Urlهایی بدون پسوند استفاده میشود. همچنین این سیستم امکان تهیه آدرسهایی با سازگاری بهتر با موتورهای جستجو را نیز از ابتدا مدنظر داشته است.
بله. این زیر ساخت در اختیار وب فرمها نیز هست؛ اما فرق است بین حالتی که از ابتدا مجبور شویم تمیزتر کار کنیم با زمانیکه این انتخاب را داریم و ... عموما هم از آن در وب فرمها استفاده نمیشود.
2- عدم وابستگی الزامی به فایلهای فیزیکی موجود در سیستم
کلیه درخواستها در MVC برخلاف وب فرمها در بدو امر به فایلهای موجود در سیستم منتقل نمیشوند. درخواستها به متدهای موجود در کنترلرها منتقل میشوند. همین مساله سبب میشود که آدرسها الزاما به یک فایل فیزیکی موجود در سیستم اشاره نکنند. به این ترتیب میتوان درخواستها را بر اساس شرایط، به Viewهای مختلف هدایت کرد و نه اینکه هر درخواست ابتدا به یک view رسیده و سپس به متدی ارجاع داده شود.
این مساله از لحاظ امنیتی نیز مهم است. درخواستهای رسیده به MVC سبب اجرای هیچ فرمی نخواهند شد. درخواستها حتما باید از فیلتر یک کنترلر عبور کنند تا اجرایی شوند.
3- امکان مدیریت بهتر قسمتهای مختلف سایت در پوشههای جداگانه
اگر به سورس اکثر سایتهای مبتنی بر ASP.NET Web forms توجه کنیم، تمام فایلهای آنها در ریشه سایت قرار دارند منهای فایلهای CSS و JS و تصاویر. در ASP.NET MVC از ابتدای کار، هر قسمت از سایت در پوشههای جداگانهای قرار میگیرد و به این ترتیب مدیریت فایلها و نظم دهی به آنها سادهتر خواهد بود.
4- امکان تعریف تمام اجزای یک فرم یا view به صورت strongly typed
در ASP.NET MVC میتوان یک کلاس را به یک فرم یا View نسبت داد. به این ترتیب کنترلهای web forms تبدیل به خواص این کلاس در MVC خواهند شد. مزیت این امر امکان کنترل تمام اجزای فرمهای سایت توسط کامپایلر است.
به این ترتیب اگر در طی یک حلقه، جدولی را ایجاد کنیم، تمام عناصر تشکیل دهنده این حلقه (چه کدهای آن و چه المانهایی که اطلاعات را در صفحه نمایش میدهند) نیز توسط کامپایلر قابل بررسی و خطایابی هستند.
5- مقدار دهی خودکار مدل متناظر با یک فرم یا View در ASP.NET MVC
روال متداول کار با وب فرمها، قرار دادن تعدادی کنترل در صفحه و سپس دریافت دستی مقادیر آنها در فایل code behind است. در MVC دیگر نیازی نیست تا این کارها را دستی انجام دهید. به یک فرم یا View کلاسی را انتساب خواهید داد. فریم ورک خواص آنرا به صورت خودکار در حین ارسال به سرور مقدار دهی خواهد کرد. این مورد حتی در حین کار با jQuery Ajax نیز صادق است.
این مساله کار با ORMها را نیز سادهتر میکند. از این جهت که تمام آنها نهایتا با یک سری مدل و کلاس کار خواهند کرد و تمام فیلدها و جداول به تعدادی کلاس و خاصیت تعریف شده در آنها نگاشت میشوند.
به این ترتیب چون دیگر نیازی به ارجاع مستقیم به اشیاء بصری در فایلهای code behind که در اینجا کنترلر نام گرفتهاند نیست، نوشتن آزمون واحد برای این کلاسها نیز به شدت سادهتر شده است.
6- ASP.NET MVC به همراه یک فرم ساز توکار ارائه میشود
اگر کسی به شما گفته است که سرعت کار با ASP.NET MVC پایین است به طور قطع دو فصل اول یک کتاب MVC را بیشتر مطالعه نکرده است. در MVC یک کلاس متناظر با فرمی را طراحی میکنید. توسط ابزار scaffolding همراه با VS.NET از روی این کلاس و مدل، با چند کلیک یک فرم تولید خواهد شد. فرمی که حتی مقدار دهی و انتساب عناصر بصری آن به کلاس متناظر با آن نیز خودکار است.
سرعت پیاده سازی یک برنامه با ASP.NET MVC به مراتب بیشتر است از کار با وب فرمها.
7- حذف View State در MVC
از آنجائیکه فرمهای ASP.NET Web forms از نوع strongly typed نیستند (در دات نت 4 و نیم اندکی بهبود در حد گریدهای آن حاصل شده البته)، برای اینکه پس از ارسال یک فرم به سرور باز هم کنترلهای نمایش داده شده در صفحه همان مقادیر قبلی را نمایش دهند، مکانیزم View State به همراه ذخیره سازی اطلاعات فرم در فیلدهای مخفی فرم جاری طراحی شد.
در MVC نیازی به این مکانیزم نیست. زیرا فقط کافی است که اطلاعات مدل را مجددا به View ارسال کنیم. نمایش و انتساب نهایی آن در اینجا خودکار است. بنابراین نیازی به View State وجود ندارد.
8- کنترل بهتر بر روی اعتبار سنجی اطلاعات دریافتی
در وب فرمها اگر بخواهیم سیستم اعتبارسنجی آنرا غیرفعال کنیم، مثلا برای دریافت html از کاربر، نیاز است کلا آنرا از کار بیندازیم (یا در سطح فرم یا در سطح کل برنامه). در MVC میتوان این اعتبار سنجی را تنها در سطح یک خاصیت که قرار است اطلاعات HTML ایی را دریافت کند، غیرفعال کرد؛ نه برای کل فرم و نه در سطح کل برنامه.
9- امکان استفاده از فرمهای و Viewهای Razor بجای موتور وب فرمها
در وب فرمها تا این زمان فقط محدود به تنها موتور نمایشی مخصوص به آن هستیم. اما در MVC این محدودیت برداشته شده و تا به حال چندین و چند View engine در این بین توسط مایکروسافت و سایر برنامه نویسها طراحی شده است. مهمترین آنها Razor است که تمام برنامه نویسهای MVC پس از مدتی به روان بودن و طراحی طبیعی و عالی آن اعتراف دارند.
10- امکان تعریف بیش از یک فرم در صفحه
طراحی ASP.NET Web forms از روز اول آن محدود به یک فرم در صفحه بوده است. این محدودیت در MVC برداشته شده و مزیت آن امکان ارسال اطلاعات قسمتهای مختلف یک فرم به کنترلرهای مختلف و جداسازی بهتر کدهای قسمتهای مختلف برنامه است.
11- امکان Refactoring بهتر کدهای تکراری در ASP.NET MVC به کمک مفهوم فیلترها
فیلترها در MVC یک سری attribute هستند که میتوان آنها را به متدهای کنترلرها اعمال کرد و به صورت خودکار توسط فریم ورک پیش یا پس از اجرای یک متد اجرا میشوند. به این ترتیب حجم قابل ملاحظهای از if و elseها را میتوان در این فیلترها کپسوله کرد و کدهای متدهای کنترلرها را تمیزتر و زیباتر نمود.
12- سازگاری کامل با jQuery و jQuery Ajax و کلا انواع و اقسام فریمورکهای جاوا اسکریپتی
در MVC وب کنترلها کنار گذاشته شدهاند و سعی شده است با وب به همان نحو که هست برخورد شود. به این ترتیب اگر نیاز داشتید، کل دکمههای فرم را با spanها جایگزین کرده و توسط فریم ورکهای CSS ایی تزئین کنید، بدون نیاز به نگارش جدیدی از ASP.NET MVC، باز هم برنامه کار خواهد کرد.
یا برای کار با اجزای مختلف فرم از دهها و صدها افزونه موجود برای jQuery به سادگی میتوان استفاده کرد. برای نمونه کل سیستم اعتبار سنجی توکار MVC با اعتبار سنجی jQuery یکپارچه و جایگزین شده است.
یا برای کار با jQuery Ajax نیازی نیست تا متدی را static تعریف کنید و به این ترتیب از مزایای امنیتی توکار ASP.NET محروم شوید (مثلا دسترسی به شیء User اعتبار سنجی مبتنی بر فرمها). یا اگر فرم شما 10 فیلد دارد، کل این فیلدها به صورت خودکار به خواص متناظر با آنها نگاشت خواهد شد و نیازی نیست برای این مورد کد بنویسید. به علاوه باید درنظر داشت که jQuery Ajax نسبت به فریم ورک Ajax همراه با ASP.NET web forms بسیار سبکتر و سریعتر عمل میکند چون نیازی ندارد تا هر بار View state را نیز به سرور ارسال کند.
همچنین در اینجا دیگر ID کنترلهای مورد استفاده در اسکریپتها به صورت خودکار تولید نمیشوند و برنامه نویس از ابتدای امر کنترل کاملی را روی این مساله دارد.
13- امکانات فشرده سازی css و js بهتر
در MVC 4 سیستم bundling آن از نمونه مشابه موجود در وب فرمها کاملتر است و جهت فشرده سازی و یکی کردن هر دو مورد فایلهای css و js میتواند بکارگرفته شود؛ به همراه تنظیمات کش مرورگر و gzip خودکار حاصل. به علاوه این سیستم را سفارشی سازی نیز میتوان ساخت و بهینه سازی عملکرد آن مطابق نیاز میسر است.
14- یکپارچگی بهتر EF Code first با MVC
عنوان شد که وجود مدلها و فرمهای strongly typed یکی از مزایای کار با MVC است و ORMها نیز نهایتا با همین کلاسها هستند که کار میکنند. در MVC سیستم کد سازی به نام scaffolding وجود دارد (تهیه شده توسط خود مایکروسافت) که میتواند بر اساس مدلهای EF code first شما، قسمت عمدهای از کدهای یک برنامه ASP.NET MVC را تولید کند. سپس میتوانید به سفارشی سازی آن مشغول شوید.
15- تزریق وابستگیها در MVC سادهتر است
در هر دو فریم ورک وب فرمها و MVC امکان تزریق وابستگیها وجود دارد. اما در MVC میتوان در میانه کار وهله سازی کنترلرها، دخالت کرد و کنترل آن را کاملا در دست گرفت. همین امر سبب میشود حین کار با کتابخانههای تزریق وابستگیها در ASP.NET MVC حجم کد نویسی به شدت کاهش پیدا کند.
16- امکانات امنیتی MVC بیشتر است
عنوان شد که در MVC میتوان اعتبار سنجی را تنها در حد یک خاصیت غیرفعال کرد. فیلتر مبارزه با حملات CSRF جزئی از فریم ورک MVC است. به همراه فیلتر Authorize آن که باز هم اعمال سفارشی سیستم اعتبار سنجی مبتنی بر فرمها را سادهتر میکند با امکان یکپارچگی بهتر با Role providerهای سفارشی.
و یا برای نمونه Razor به صورت پیش فرض امن طراحی شده است. خروجی Razor همواره و در بدو امر، html encoded است مگر اینکه برنامه نویس آگاهانه آنرا تغییر دهد. این مورد مقاومت در برابر حملات XSS را بالا خواهد برد.
امکان استفاده از فیلترهای سفارشی که عنوان شد، جهت مسایل امنیتی بسیار کاربرد دارند. برای مثال بررسی referrer فرم ارسال به سرور را درنظر بگیرید. در وب فرمها میتوان اینکار را با یک http module که روی کل برنامه تاثیر گذار است انجام داد. اما در MVC این فیلتر را تنها میتوان بر روی یک فرم خاص عمومی برای مثال اعمال کرد و نه کل برنامه.
1- آدرسهای تمیزتر
در ASP.NET MVC به صورت پیش فرض از سیستم Routing موجود در زیر ساخت ASP.NET برای نمایش Urlهایی بدون پسوند استفاده میشود. همچنین این سیستم امکان تهیه آدرسهایی با سازگاری بهتر با موتورهای جستجو را نیز از ابتدا مدنظر داشته است.
بله. این زیر ساخت در اختیار وب فرمها نیز هست؛ اما فرق است بین حالتی که از ابتدا مجبور شویم تمیزتر کار کنیم با زمانیکه این انتخاب را داریم و ... عموما هم از آن در وب فرمها استفاده نمیشود.
2- عدم وابستگی الزامی به فایلهای فیزیکی موجود در سیستم
کلیه درخواستها در MVC برخلاف وب فرمها در بدو امر به فایلهای موجود در سیستم منتقل نمیشوند. درخواستها به متدهای موجود در کنترلرها منتقل میشوند. همین مساله سبب میشود که آدرسها الزاما به یک فایل فیزیکی موجود در سیستم اشاره نکنند. به این ترتیب میتوان درخواستها را بر اساس شرایط، به Viewهای مختلف هدایت کرد و نه اینکه هر درخواست ابتدا به یک view رسیده و سپس به متدی ارجاع داده شود.
این مساله از لحاظ امنیتی نیز مهم است. درخواستهای رسیده به MVC سبب اجرای هیچ فرمی نخواهند شد. درخواستها حتما باید از فیلتر یک کنترلر عبور کنند تا اجرایی شوند.
3- امکان مدیریت بهتر قسمتهای مختلف سایت در پوشههای جداگانه
اگر به سورس اکثر سایتهای مبتنی بر ASP.NET Web forms توجه کنیم، تمام فایلهای آنها در ریشه سایت قرار دارند منهای فایلهای CSS و JS و تصاویر. در ASP.NET MVC از ابتدای کار، هر قسمت از سایت در پوشههای جداگانهای قرار میگیرد و به این ترتیب مدیریت فایلها و نظم دهی به آنها سادهتر خواهد بود.
4- امکان تعریف تمام اجزای یک فرم یا view به صورت strongly typed
در ASP.NET MVC میتوان یک کلاس را به یک فرم یا View نسبت داد. به این ترتیب کنترلهای web forms تبدیل به خواص این کلاس در MVC خواهند شد. مزیت این امر امکان کنترل تمام اجزای فرمهای سایت توسط کامپایلر است.
به این ترتیب اگر در طی یک حلقه، جدولی را ایجاد کنیم، تمام عناصر تشکیل دهنده این حلقه (چه کدهای آن و چه المانهایی که اطلاعات را در صفحه نمایش میدهند) نیز توسط کامپایلر قابل بررسی و خطایابی هستند.
5- مقدار دهی خودکار مدل متناظر با یک فرم یا View در ASP.NET MVC
روال متداول کار با وب فرمها، قرار دادن تعدادی کنترل در صفحه و سپس دریافت دستی مقادیر آنها در فایل code behind است. در MVC دیگر نیازی نیست تا این کارها را دستی انجام دهید. به یک فرم یا View کلاسی را انتساب خواهید داد. فریم ورک خواص آنرا به صورت خودکار در حین ارسال به سرور مقدار دهی خواهد کرد. این مورد حتی در حین کار با jQuery Ajax نیز صادق است.
این مساله کار با ORMها را نیز سادهتر میکند. از این جهت که تمام آنها نهایتا با یک سری مدل و کلاس کار خواهند کرد و تمام فیلدها و جداول به تعدادی کلاس و خاصیت تعریف شده در آنها نگاشت میشوند.
به این ترتیب چون دیگر نیازی به ارجاع مستقیم به اشیاء بصری در فایلهای code behind که در اینجا کنترلر نام گرفتهاند نیست، نوشتن آزمون واحد برای این کلاسها نیز به شدت سادهتر شده است.
6- ASP.NET MVC به همراه یک فرم ساز توکار ارائه میشود
اگر کسی به شما گفته است که سرعت کار با ASP.NET MVC پایین است به طور قطع دو فصل اول یک کتاب MVC را بیشتر مطالعه نکرده است. در MVC یک کلاس متناظر با فرمی را طراحی میکنید. توسط ابزار scaffolding همراه با VS.NET از روی این کلاس و مدل، با چند کلیک یک فرم تولید خواهد شد. فرمی که حتی مقدار دهی و انتساب عناصر بصری آن به کلاس متناظر با آن نیز خودکار است.
سرعت پیاده سازی یک برنامه با ASP.NET MVC به مراتب بیشتر است از کار با وب فرمها.
7- حذف View State در MVC
از آنجائیکه فرمهای ASP.NET Web forms از نوع strongly typed نیستند (در دات نت 4 و نیم اندکی بهبود در حد گریدهای آن حاصل شده البته)، برای اینکه پس از ارسال یک فرم به سرور باز هم کنترلهای نمایش داده شده در صفحه همان مقادیر قبلی را نمایش دهند، مکانیزم View State به همراه ذخیره سازی اطلاعات فرم در فیلدهای مخفی فرم جاری طراحی شد.
در MVC نیازی به این مکانیزم نیست. زیرا فقط کافی است که اطلاعات مدل را مجددا به View ارسال کنیم. نمایش و انتساب نهایی آن در اینجا خودکار است. بنابراین نیازی به View State وجود ندارد.
8- کنترل بهتر بر روی اعتبار سنجی اطلاعات دریافتی
در وب فرمها اگر بخواهیم سیستم اعتبارسنجی آنرا غیرفعال کنیم، مثلا برای دریافت html از کاربر، نیاز است کلا آنرا از کار بیندازیم (یا در سطح فرم یا در سطح کل برنامه). در MVC میتوان این اعتبار سنجی را تنها در سطح یک خاصیت که قرار است اطلاعات HTML ایی را دریافت کند، غیرفعال کرد؛ نه برای کل فرم و نه در سطح کل برنامه.
9- امکان استفاده از فرمهای و Viewهای Razor بجای موتور وب فرمها
در وب فرمها تا این زمان فقط محدود به تنها موتور نمایشی مخصوص به آن هستیم. اما در MVC این محدودیت برداشته شده و تا به حال چندین و چند View engine در این بین توسط مایکروسافت و سایر برنامه نویسها طراحی شده است. مهمترین آنها Razor است که تمام برنامه نویسهای MVC پس از مدتی به روان بودن و طراحی طبیعی و عالی آن اعتراف دارند.
10- امکان تعریف بیش از یک فرم در صفحه
طراحی ASP.NET Web forms از روز اول آن محدود به یک فرم در صفحه بوده است. این محدودیت در MVC برداشته شده و مزیت آن امکان ارسال اطلاعات قسمتهای مختلف یک فرم به کنترلرهای مختلف و جداسازی بهتر کدهای قسمتهای مختلف برنامه است.
11- امکان Refactoring بهتر کدهای تکراری در ASP.NET MVC به کمک مفهوم فیلترها
فیلترها در MVC یک سری attribute هستند که میتوان آنها را به متدهای کنترلرها اعمال کرد و به صورت خودکار توسط فریم ورک پیش یا پس از اجرای یک متد اجرا میشوند. به این ترتیب حجم قابل ملاحظهای از if و elseها را میتوان در این فیلترها کپسوله کرد و کدهای متدهای کنترلرها را تمیزتر و زیباتر نمود.
12- سازگاری کامل با jQuery و jQuery Ajax و کلا انواع و اقسام فریمورکهای جاوا اسکریپتی
در MVC وب کنترلها کنار گذاشته شدهاند و سعی شده است با وب به همان نحو که هست برخورد شود. به این ترتیب اگر نیاز داشتید، کل دکمههای فرم را با spanها جایگزین کرده و توسط فریم ورکهای CSS ایی تزئین کنید، بدون نیاز به نگارش جدیدی از ASP.NET MVC، باز هم برنامه کار خواهد کرد.
یا برای کار با اجزای مختلف فرم از دهها و صدها افزونه موجود برای jQuery به سادگی میتوان استفاده کرد. برای نمونه کل سیستم اعتبار سنجی توکار MVC با اعتبار سنجی jQuery یکپارچه و جایگزین شده است.
یا برای کار با jQuery Ajax نیازی نیست تا متدی را static تعریف کنید و به این ترتیب از مزایای امنیتی توکار ASP.NET محروم شوید (مثلا دسترسی به شیء User اعتبار سنجی مبتنی بر فرمها). یا اگر فرم شما 10 فیلد دارد، کل این فیلدها به صورت خودکار به خواص متناظر با آنها نگاشت خواهد شد و نیازی نیست برای این مورد کد بنویسید. به علاوه باید درنظر داشت که jQuery Ajax نسبت به فریم ورک Ajax همراه با ASP.NET web forms بسیار سبکتر و سریعتر عمل میکند چون نیازی ندارد تا هر بار View state را نیز به سرور ارسال کند.
همچنین در اینجا دیگر ID کنترلهای مورد استفاده در اسکریپتها به صورت خودکار تولید نمیشوند و برنامه نویس از ابتدای امر کنترل کاملی را روی این مساله دارد.
13- امکانات فشرده سازی css و js بهتر
در MVC 4 سیستم bundling آن از نمونه مشابه موجود در وب فرمها کاملتر است و جهت فشرده سازی و یکی کردن هر دو مورد فایلهای css و js میتواند بکارگرفته شود؛ به همراه تنظیمات کش مرورگر و gzip خودکار حاصل. به علاوه این سیستم را سفارشی سازی نیز میتوان ساخت و بهینه سازی عملکرد آن مطابق نیاز میسر است.
14- یکپارچگی بهتر EF Code first با MVC
عنوان شد که وجود مدلها و فرمهای strongly typed یکی از مزایای کار با MVC است و ORMها نیز نهایتا با همین کلاسها هستند که کار میکنند. در MVC سیستم کد سازی به نام scaffolding وجود دارد (تهیه شده توسط خود مایکروسافت) که میتواند بر اساس مدلهای EF code first شما، قسمت عمدهای از کدهای یک برنامه ASP.NET MVC را تولید کند. سپس میتوانید به سفارشی سازی آن مشغول شوید.
15- تزریق وابستگیها در MVC سادهتر است
در هر دو فریم ورک وب فرمها و MVC امکان تزریق وابستگیها وجود دارد. اما در MVC میتوان در میانه کار وهله سازی کنترلرها، دخالت کرد و کنترل آن را کاملا در دست گرفت. همین امر سبب میشود حین کار با کتابخانههای تزریق وابستگیها در ASP.NET MVC حجم کد نویسی به شدت کاهش پیدا کند.
16- امکانات امنیتی MVC بیشتر است
عنوان شد که در MVC میتوان اعتبار سنجی را تنها در حد یک خاصیت غیرفعال کرد. فیلتر مبارزه با حملات CSRF جزئی از فریم ورک MVC است. به همراه فیلتر Authorize آن که باز هم اعمال سفارشی سیستم اعتبار سنجی مبتنی بر فرمها را سادهتر میکند با امکان یکپارچگی بهتر با Role providerهای سفارشی.
و یا برای نمونه Razor به صورت پیش فرض امن طراحی شده است. خروجی Razor همواره و در بدو امر، html encoded است مگر اینکه برنامه نویس آگاهانه آنرا تغییر دهد. این مورد مقاومت در برابر حملات XSS را بالا خواهد برد.
امکان استفاده از فیلترهای سفارشی که عنوان شد، جهت مسایل امنیتی بسیار کاربرد دارند. برای مثال بررسی referrer فرم ارسال به سرور را درنظر بگیرید. در وب فرمها میتوان اینکار را با یک http module که روی کل برنامه تاثیر گذار است انجام داد. اما در MVC این فیلتر را تنها میتوان بر روی یک فرم خاص عمومی برای مثال اعمال کرد و نه کل برنامه.
سلام،
گاهی در پروژههای blazor نیاز میشود که از صفحات razor به جای کامپوننتهای razor استفاده نمود.(درحقیقت از فایلهای a.cshtml به جای a.razor)
آیا راهی هست که هم صفحات و هم کامپوننتهای razor فقط از یک layout استفاده کنند یا اینکه مجبوریم برای صفحات razor یک layout جداگانه درنظر بگیریم؟
با سپاس فراوان.
پیشنیاز
«تبدیل HTML به PDF با استفاده از کتابخانهی iTextSharp»
هرچند کلاس HTMLWorker دیگر توسعه نخواهد یافت (با کتابخانه XML Worker جایگزین شدهاست)، اما برای تبدیل یک سری از کارهای ابتدایی بسیار مناسب است. در این بین اگر تگ خاصی توسط کلاس HTMLWorker پشتیبانی نشود یا پیاده سازی آن ناقص باشد، امکان جایگزین کردن کامل آن با پیاده سازی اینترفیس IHTMLTagProcessor وجود دارد. در کدهای ذیل نحوه جایگزین کردن پردازش کننده تصاویر آنرا ملاحظه میکنید. در اینجا پشتیبانی از تصاویر base64 مدفون شده در صفحات html به آن اضافه شده است:
همانطور که ملاحظه میکنید، پس از پیاده سازی اینترفیس IHTMLTagProcessor و تهیه یک پردازش کننده جدید که اینبار میتواند تصاویر شروع شده با data:image را مورد استفاده قرار دهد، برای معرفی آن به کتابخانه HTMLWorker فقط کافی است وهلهای از HTMLTagProcessors موجود را ایجاد نمائیم و سپس در این Dictionary، نمونه قدیمی را جایگزین کنیم:
در ادامه فقط کافی است لیست جدید پردازندهها را به متد ParseToList ارسال نمائیم تا مورد استفاده قرار گیرد:
«تبدیل HTML به PDF با استفاده از کتابخانهی iTextSharp»
هرچند کلاس HTMLWorker دیگر توسعه نخواهد یافت (با کتابخانه XML Worker جایگزین شدهاست)، اما برای تبدیل یک سری از کارهای ابتدایی بسیار مناسب است. در این بین اگر تگ خاصی توسط کلاس HTMLWorker پشتیبانی نشود یا پیاده سازی آن ناقص باشد، امکان جایگزین کردن کامل آن با پیاده سازی اینترفیس IHTMLTagProcessor وجود دارد. در کدهای ذیل نحوه جایگزین کردن پردازش کننده تصاویر آنرا ملاحظه میکنید. در اینجا پشتیبانی از تصاویر base64 مدفون شده در صفحات html به آن اضافه شده است:
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using iTextSharp.text; using iTextSharp.text.html; using iTextSharp.text.html.simpleparser; using iTextSharp.text.pdf; namespace CustomHtmlWorkerTag { /// <summary> /// Our custom HTML Tag to add an IElement. /// </summary> public class CustomImageHTMLTagProcessor : IHTMLTagProcessor { /// <summary> /// Tells the HTMLWorker what to do when a close tag is encountered. /// </summary> public void EndElement(HTMLWorker worker, string tag) { } /// <summary> /// Tells the HTMLWorker what to do when an open tag is encountered. /// </summary> public void StartElement(HTMLWorker worker, string tag, IDictionary<string, string> attrs) { Image image; var src = attrs["src"]; if (src.StartsWith("data:image/")) { // data:[<MIME-type>][;charset=<encoding>][;base64],<data> var base64Data = src.Substring(src.IndexOf(",") + 1); var imagedata = Convert.FromBase64String(base64Data); image = Image.GetInstance(imagedata); } else { image = Image.GetInstance(src); } worker.UpdateChain(tag, attrs); worker.ProcessImage(image, attrs); worker.UpdateChain(tag); } } class Program { static void Main(string[] args) { using (var pdfDoc = new Document(PageSize.A4)) { PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create)); pdfDoc.Open(); FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf"); var tags = new HTMLTagProcessors(); // Replace the built-in image processor tags[HtmlTags.IMG] = new CustomImageHTMLTagProcessor(); var html = "<img alt='' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAodJREFUeNpsk0tME1EUhv87UwlCREhRFpi4cGMMRrTE4MaoxBhAsDyMssFHfCQu3BlXGuNKNy5NmqALoqEEMJWCgEUjYojllSpofIUNBNqmIKU6OnQennunUxvgJF86957z/+d27hkGigMlDJfOAmV7AcYsKGqIZljRSvhNE+CMTwEtXmBy2gQb7mCQJUBKkTIQYtfJYCNMAxO9hzq5CYmFiWFY6ISE9VFLRedc1SONeqwf+uJLuKreNPI9nltbLG0orhpqUCM90DRVoEbJ5MSLho1MMg1O0bHOuyoD9crCcxL+xa0HqwL+rEQHsb/CW89reO1aAyEuq+yp+zXvg66rgng8LrDXSmwYpUc8dZkmDsJNL+NCeVVXbWK+O32cpJ7E6OgkwuEwrl8phaHrVsfYD+x03XTPjN3nzZnD0HGxvPppTSLcLwo0I4lldRFK8jdCoZBlJquAbBnr0BD9GUTRvubahclW5qDukqkpIqlodGQ1At3UxZXaIUvauqsyjBV+jZJEJ3s83HO5j+UWI7E6C4mp2EQCTixyV2CvbbKzNmN2zNfHtbzPM3p4FOy/M5CXtwsOKZmmsOi2IHMvyyFhJhgY4BqutQ/aRRstocEngZzswnQnO+x1lqTjy8hIgNdyDc+x5nomxrKJhpcSp2lSrx48WlZhGArynG5hsLLoE7/jQ59f0aR7ZBkdbf7U6Ge+mKYaBvdx8wwZXjtWvfswfTrp3Over29J8NAXYO1t/v/7csZA5U5/Q35nH+aKt8OMR2POPSUFOyRmorvje3BiCt4b9zBANTmwGvP/aMoZRluJbURB8APmnPlQliNLzk8flxbeh9Du8eId5bYQ2SnxH36b/wQYABNFRsIaESsTAAAAAElFTkSuQmCC' />"; var styles = new StyleSheet(); styles.LoadTagStyle(HtmlTags.BODY, HtmlTags.FONTFAMILY, "tahoma"); styles.LoadTagStyle(HtmlTags.BODY, HtmlTags.ENCODING, "Identity-H"); PdfPCell pdfCell = new PdfPCell { Border = 0 }; pdfCell.RunDirection = PdfWriter.RUN_DIRECTION_LTR; using (var reader = new StringReader(html)) { var parsedHtmlElements = HTMLWorker.ParseToList(reader, styles, tags, null); foreach (var htmlElement in parsedHtmlElements) { pdfCell.AddElement(htmlElement); } } var table1 = new PdfPTable(1); table1.AddCell(pdfCell); pdfDoc.Add(table1); } Process.Start("Test.pdf"); } } }
var tags = new HTMLTagProcessors(); // Replace the built-in image processor tags[HtmlTags.IMG] = new CustomImageHTMLTagProcessor();
HTMLWorker.ParseToList(reader, styles, tags, null)
نظرات اشتراکها
کاغذ دیواریهای Bing با تقویم شمسی
به روز رسانی
- افزایش میزان تایم آوت برای خطوط کند دریافتی از اینترنت
- اضافه شدن گزینهی UseRandomImages به فایل config آن برای نمایش تصاویر اتفاقی دریافتی (حالت پیش فرض آن، نمایش آخرین تصویر دریافتی است)
- اضافه شدن پشتیبانی از ویندوز 7 (تصاویر png را به عنوان wallpaper قبول نمیکند)
- افزایش میزان تایم آوت برای خطوط کند دریافتی از اینترنت
- اضافه شدن گزینهی UseRandomImages به فایل config آن برای نمایش تصاویر اتفاقی دریافتی (حالت پیش فرض آن، نمایش آخرین تصویر دریافتی است)
- اضافه شدن پشتیبانی از ویندوز 7 (تصاویر png را به عنوان wallpaper قبول نمیکند)
نکته ای بسیار مهم در رابطه با کار با Backload:
حذف و اضافه کردن تصاویر با Backload روی لوکال بدون هیچ مشکلی انجام میشود اما بعد از اینکه شما پروژه را آپلود کردید خواهید دید که تصاویر اضافه میشوند اما حذف نمیشوند.
برای حل این مشکل کد زیر را در تگ <system.webServer> در فایل web.config قرار دهید :
موفق باشید .
حذف و اضافه کردن تصاویر با Backload روی لوکال بدون هیچ مشکلی انجام میشود اما بعد از اینکه شما پروژه را آپلود کردید خواهید دید که تصاویر اضافه میشوند اما حذف نمیشوند.
برای حل این مشکل کد زیر را در تگ <system.webServer> در فایل web.config قرار دهید :
<modules runAllManagedModulesForAllRequests="true"> <remove name="WebDAVModule"/> </modules>
موفق باشید .
روی محتوای پویای سایت شما اثری ندارد. فقط تصاویر ، css و فایلهای جاوا اسکریپت و امثال آنرا کش میکند (که عموما پویا نیستند). بنابراین برای خیلی از سایتها مفید است.
و همچنین در خیلی از سایتها هم تغییرات css یا تصاویر اصلی سایت، شاید ماهی یکبار باشد. زمانیکه طراحی قالب و یک سری از موارد ثابت سایت تمام شد، اینها دیگر هر روز قرار نیست تغییری کنند.
و همچنین در خیلی از سایتها هم تغییرات css یا تصاویر اصلی سایت، شاید ماهی یکبار باشد. زمانیکه طراحی قالب و یک سری از موارد ثابت سایت تمام شد، اینها دیگر هر روز قرار نیست تغییری کنند.
Decorators یا تزئین کنندهها و ReflectDecorators، یکی از پیشنهادهای نگارش بعدی جاوا اسکریپت هستند (ECMAScript 2016) که هم اکنون قابلیت استفادهی از آنها در TypeScript وجود دارد.
جهت افزودن قابلیتهای meta-programming به زبانهای جاوا اسکریپت و TypeScript و همچنین تعریف annotations بر روی کلاسها و اعضای کلاس، میتوان از Decorators استفاده کرد. یک decorator، تعریف ویژهای است که میتواند به تعریف یک کلاس، متد، خاصیت و یا پارامتر «متصل» شود و به صورت expression@ تعریف میگردد. این expression باید قابلیت فراخوانی به صورت یک متد را داشته باشد که در زمان اجرا فراخوانی خواهد شد. از Decorators در طراحی AngularJS 2 زیاد استفاده شدهاست.
نحوهی فعال سازی Decorators با ES 5
این قابلیت فعلا در مرحلهی آزمایش به سر میبرد؛ بنابراین برای فعال سازی آن نیاز است پارامترهای experimentalDecorators و emitDecoratorMetadata را به کامپایلر خط فرمان tsc و یا به خواص کامپایلر در فایل tsconfig.json اضافه کنید:
بررسی انواع Decorators
در اینجا یک مثال ساده از decoratorها را مشاهده میکنید:
و سادهترین فرم پیاده سازی این decoratorExpression به صورت زیر است:
همانطور که ملاحظه میکنید، یک decorator در اصل یک function است که دسترسی به target ایی را که قرار است تزئین شود، میسر میکند. این قابلیت بسیار شبیه است به مفهومی به نام attributes و annotations در زبان #C.
در ادامه انواع و اقسام decoratorهای ممکن را با مثالهایی بررسی خواهیم کرد.
Class Decorator
یک class Decorator، پیش از تعریف یک کلاس اضافه میشود و هدف آن، اعمال تعاریفی به سازندهی کلاس است و از آن میتوان جهت تحت نظر قرار دادن تعاریف کلاس و یا تغییر یا حتی تعویض کلی تعاریف آن استفاده کرد. یک class Decorator را نمیتوان در سایر فایلهای تعاریف، مانند d.ts. قرار داد.
تنها آرگومانی که به یک class Decorator ارسال میشود، سازندهی کلاسی است که به آن اعمال شدهاست. اگر متد class Decorator مقداری را برگرداند، سبب تعویض و جایگزینی تعاریف کلاس با مقدار باگشت داده شده، میشود.
در ادامه دو مثال را از class Decoratorها مشاهده میکنید:
مثال بدون پارامتر
اگر به تصویر فوق و خروجی نهایی آن دقت کنید، مشاهده میکنید که یک decorator برای اجرا، نیازی به وهله سازی از آن کلاس ندارد و اجرای آن دقیقا در زمان تعریف کلاس انجام میشود. این اجرا به معنای تزریق کدهای تزئین کننده، به تعاریف سازندهی کلاس تعریف شده، پیش از وهله سازی از آن است.
مثال با پارامتر
با خروجی
همانطور که در این مثال مشاهده میکنید، چندین decorator را نیز میتوان به تعاریف یک کلاس اعمال کرد که به آن Decorator Composition نیز میگویند.
Property Decorator
یک Property Decorator دقیقا پیش از تعریف یک خاصیت اضافه میشود و نباید در سایر فایلهای تعاریف جانبی قرار گیرد. زمانیکه متد expression آن در runtime فراخوانی میشود، دو پارامتر را دریافت خواهد کرد:
الف) برای static member ها، متد سازندهی کلاس و برای instance memberها، prototype کلاس را دریافت میکند.
ب) نام عضو.
اگر متد expression تعریف شده، مقداری را برگرداند، به عنوان Property Descriptor آن عضو بکارگرفته میشود.
در مثال ذیل که متد PropertyDecorator به خاصیت name اعمال شدهاست، دو پارامتر ارسالی به آنرا بهتر میتوان مشاهده کرد:
با خروجی
Method Decorator
یک Method Decorator باید درست پیش از تعریف یک متد، قرارگیرد و نباید در سایر کلاسها تعاریف نوعهای جانبی افزوده شود. هدف از آن میتواند بررسی، مشاهده و تغییر رفتار یک متد باشد. به متد expression آن، سه پارامتر ارسال میشوند:
الف) برای static member ها، متد سازندهی کلاس و برای instance memberها، prototype کلاس را دریافت میکند.
ب) نام عضو
ج) Property Descriptor عضو
اگر متد تعریف شده، خروجی را برگرداند به عنوان Property Descriptor آن متد استفاده خواهد شد.
در مثال ذیل، متد تزئین کنندهای به نام MethodDecorator تعریف شدهاست که سه پارامتر یاد شده را در زمان اجرا دریافت میکند:
با خروجی
و مثالی جهت محدود ساختن آن به یک سری متد خاص که یک پارامتر عددی را دریافت میکنند و یک خروجی عددی نیز دارند:
با خروجی
و مثالی از تزئین کنندههای متدهای استاتیک یک کلاس که در این حالت، پارامتر target از نوع متد سازندهی کلاس خواهد بود و نه prototype آن:
با خروجی
Parameter Decorator
یک Parameter Decorator باید درست پیش از تعریف یک آرگومان متد، قرارگیرد و نباید در سایر کلاسها تعاریف نوعهای جانبی افزوده شود. به متد expression آن سه پارامتر ارسال میشوند:
الف) برای static member ها، متد سازندهی کلاس و برای instance memberها، prototype کلاس را دریافت میکند.
ب) نام عضو
ج) شماره ایندکس پارامتر مدنظر در متد
از خروجی متد تزئین کننده در اینجا صرفنظر میشود.
در ادامه مثالی را از نحوهی تعریف یک تزئین کنندهی پارامترها را با سه آرگومان ویژهی آن، مشاهده میکنید:
با خروجی
یک سری مثال تکمیلی
جهت افزودن قابلیتهای meta-programming به زبانهای جاوا اسکریپت و TypeScript و همچنین تعریف annotations بر روی کلاسها و اعضای کلاس، میتوان از Decorators استفاده کرد. یک decorator، تعریف ویژهای است که میتواند به تعریف یک کلاس، متد، خاصیت و یا پارامتر «متصل» شود و به صورت expression@ تعریف میگردد. این expression باید قابلیت فراخوانی به صورت یک متد را داشته باشد که در زمان اجرا فراخوانی خواهد شد. از Decorators در طراحی AngularJS 2 زیاد استفاده شدهاست.
نحوهی فعال سازی Decorators با ES 5
این قابلیت فعلا در مرحلهی آزمایش به سر میبرد؛ بنابراین برای فعال سازی آن نیاز است پارامترهای experimentalDecorators و emitDecoratorMetadata را به کامپایلر خط فرمان tsc و یا به خواص کامپایلر در فایل tsconfig.json اضافه کنید:
Command Line: tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata tsconfig.json: { "compilerOptions": { "target": "ES5", "experimentalDecorators": true, "emitDecoratorMetadata": true } }
بررسی انواع Decorators
در اینجا یک مثال ساده از decoratorها را مشاهده میکنید:
// A simple decorator @decoratorExpression class MyClass { }
function decoratorExpression(target) { // Add a property on target target.annotated = true; }
در ادامه انواع و اقسام decoratorهای ممکن را با مثالهایی بررسی خواهیم کرد.
Class Decorator
یک class Decorator، پیش از تعریف یک کلاس اضافه میشود و هدف آن، اعمال تعاریفی به سازندهی کلاس است و از آن میتوان جهت تحت نظر قرار دادن تعاریف کلاس و یا تغییر یا حتی تعویض کلی تعاریف آن استفاده کرد. یک class Decorator را نمیتوان در سایر فایلهای تعاریف، مانند d.ts. قرار داد.
تنها آرگومانی که به یک class Decorator ارسال میشود، سازندهی کلاسی است که به آن اعمال شدهاست. اگر متد class Decorator مقداری را برگرداند، سبب تعویض و جایگزینی تعاریف کلاس با مقدار باگشت داده شده، میشود.
در ادامه دو مثال را از class Decoratorها مشاهده میکنید:
مثال بدون پارامتر
function ClassDecorator( target: Function // The class the decorator is declared on ) { console.log("ClassDecorator called on: ", target); } @ClassDecorator class ClassDecoratorExample { }
اگر به تصویر فوق و خروجی نهایی آن دقت کنید، مشاهده میکنید که یک decorator برای اجرا، نیازی به وهله سازی از آن کلاس ندارد و اجرای آن دقیقا در زمان تعریف کلاس انجام میشود. این اجرا به معنای تزریق کدهای تزئین کننده، به تعاریف سازندهی کلاس تعریف شده، پیش از وهله سازی از آن است.
مثال با پارامتر
function ClassDecoratorParams(param1: number, param2: string) { return function( target: Function // The class the decorator is declared on ) { console.log("ClassDecoratorParams(" + param1 + ", '" + param2 + "') called on: ", target); } } @ClassDecoratorParams(1, "a") @ClassDecoratorParams(2, "b") class ClassDecoratorParamsExample { }
ClassDecoratorParams(2, 'b') called on: function ClassDecoratorParamsExample() { } ClassDecoratorParams(1, 'a') called on: function ClassDecoratorParamsExample() { }
Property Decorator
یک Property Decorator دقیقا پیش از تعریف یک خاصیت اضافه میشود و نباید در سایر فایلهای تعاریف جانبی قرار گیرد. زمانیکه متد expression آن در runtime فراخوانی میشود، دو پارامتر را دریافت خواهد کرد:
الف) برای static member ها، متد سازندهی کلاس و برای instance memberها، prototype کلاس را دریافت میکند.
ب) نام عضو.
اگر متد expression تعریف شده، مقداری را برگرداند، به عنوان Property Descriptor آن عضو بکارگرفته میشود.
در مثال ذیل که متد PropertyDecorator به خاصیت name اعمال شدهاست، دو پارامتر ارسالی به آنرا بهتر میتوان مشاهده کرد:
function PropertyDecorator( target: Object, // The prototype of the class propertyKey: string | symbol // The name of the property ) { console.log("PropertyDecorator called on: ", target, propertyKey); } class PropertyDecoratorExample { @PropertyDecorator name: string; }
PropertyDecorator called on: PropertyDecoratorExample {} name
Method Decorator
یک Method Decorator باید درست پیش از تعریف یک متد، قرارگیرد و نباید در سایر کلاسها تعاریف نوعهای جانبی افزوده شود. هدف از آن میتواند بررسی، مشاهده و تغییر رفتار یک متد باشد. به متد expression آن، سه پارامتر ارسال میشوند:
الف) برای static member ها، متد سازندهی کلاس و برای instance memberها، prototype کلاس را دریافت میکند.
ب) نام عضو
ج) Property Descriptor عضو
اگر متد تعریف شده، خروجی را برگرداند به عنوان Property Descriptor آن متد استفاده خواهد شد.
در مثال ذیل، متد تزئین کنندهای به نام MethodDecorator تعریف شدهاست که سه پارامتر یاد شده را در زمان اجرا دریافت میکند:
function MethodDecorator( target: Object, // The prototype of the class propertyKey: string, // The name of the method descriptor: TypedPropertyDescriptor<any> ) { console.log("MethodDecorator called on: ", target, propertyKey, descriptor); } class MethodDecoratorExample { @MethodDecorator method() { } }
MethodDecorator called on: MethodDecoratorExample { method: [Function] } method { value: [Function], writable: true, enumerable: true, configurable: true }
و مثالی جهت محدود ساختن آن به یک سری متد خاص که یک پارامتر عددی را دریافت میکنند و یک خروجی عددی نیز دارند:
function TypeRestrictedMethodDecorator( target: Object, // The prototype of the class propertyKey: string, // The name of the method descriptor: TypedPropertyDescriptor<(num: number) => number> ) { console.log("TypeRestrictedMethodDecorator called on: ", target, propertyKey, descriptor); } class TypeRestrictedMethodDecoratorExample { @TypeRestrictedMethodDecorator method(num: number): number { return 0; } }
TypeRestrictedMethodDecorator called on: TypeRestrictedMethodDecoratorExample { method: [Function] } method { value: [Function], writable: true, enumerable: true, configurable: true }
و مثالی از تزئین کنندههای متدهای استاتیک یک کلاس که در این حالت، پارامتر target از نوع متد سازندهی کلاس خواهد بود و نه prototype آن:
function StaticMethodDecorator( target: Function, // the function itself and not the prototype propertyKey: string | symbol, // The name of the static method descriptor: TypedPropertyDescriptor<any> ) { console.log("StaticMethodDecorator called on: ", target, propertyKey, descriptor); } class StaticMethodDecoratorExample { @StaticMethodDecorator static staticMethod() { } }
StaticMethodDecorator called on: function StaticMethodDecoratorExample() { } staticMethod { value: [Function], writable: true, enumerable: true, configurable: true }
Parameter Decorator
یک Parameter Decorator باید درست پیش از تعریف یک آرگومان متد، قرارگیرد و نباید در سایر کلاسها تعاریف نوعهای جانبی افزوده شود. به متد expression آن سه پارامتر ارسال میشوند:
الف) برای static member ها، متد سازندهی کلاس و برای instance memberها، prototype کلاس را دریافت میکند.
ب) نام عضو
ج) شماره ایندکس پارامتر مدنظر در متد
از خروجی متد تزئین کننده در اینجا صرفنظر میشود.
در ادامه مثالی را از نحوهی تعریف یک تزئین کنندهی پارامترها را با سه آرگومان ویژهی آن، مشاهده میکنید:
function ParameterDecorator( target: Function, // The prototype of the class propertyKey: string | symbol, // The name of the method parameterIndex: number // The index of parameter in the list of the function's parameters ) { console.log("ParameterDecorator called on: ", target, propertyKey, parameterIndex); } class ParameterDecoratorExample { method(@ParameterDecorator param1: string, @ParameterDecorator param2: number) { } }
ParameterDecorator called on: ParameterDecoratorExample { method: [Function] } method 1 ParameterDecorator called on: ParameterDecoratorExample { method: [Function] } method 0
یک سری مثال تکمیلی