اشتراکها
اشتراکها
دانلود چندین فایل در Asp.net
اشتراکها
تزریق وابستگی در ASP.NET Core
از Claimها کمک بگیرید (در نهایت در کوکی کاربر ذخیره خواهند شد ) :
متد AddClaim برای کلاس ClaimsIdentity در نظر گرفته شده است.
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(ApplicationUser applicationUser) { // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType var userIdentity = await CreateIdentityAsync(applicationUser, DefaultAuthenticationTypes.ApplicationCookie); // Add custom user claims here userIdentity.AddClaim(new Claim("Avatar",applicationUser.Avatar)); return userIdentity; }
یا حتی میتوانید یک ClaimsIdentityFactory شخصی سازی شده در نظر بگیرید؛ برای مثال در پروژه " طراحی فریمورک برای کار با Asp.net MVC و EF به صورت NTier " یک نمونه پیاده سازی شده را میتوانید مشاهده کنید.
یک نکته تکمیلی
حین استفاده از ControllerBase میتوانیم از یک تعداد helper برای خروجی اکشنها استفاده کنیم؛ به عنوان مثال میتوانیم بعد از ساخت یک resource جدید از CreatedAtAction استفاده کنیم که به صورت خودکار خروجی 201 را برمیگرداند و همچنین هدر Location را نیز برایمان تنظیم خواهد کرد؛ [ApiController] [Route("api/users")] public class UserController : ControllerBase { [HttpGet("{id:int}")] public ActionResult GetUserById(int id) { var user = new { Id = 1, Name = "Sirwan Afifi" }; return Ok(user); } [HttpPost] public ActionResult CreateUser(CreateUserDto user) { return CreatedAtAction(nameof(GetUserById), new { user.Id }, user); } }
اطلاعات بیشتر (+)
AspNetCore.Unobtrusive.Ajax
Unobtrusive Ajax Helpers (like MVC5 Ajax.BeignForm and Ajax.ActionLink) for ASP.NET Core
Features
- Works with Upload file.
- Works with
[AntiForgeryTokenValidation]
. - Has
[AjaxOnly]
attribute to limit actions. - Has
httpRequest.IsAjaxRequest()
extension method to check request is ajax and decide return PartialView or JSON result. - Additional overloads for ease of use.
- Adds necessary JS script automatically when you use a Ajax Helpers and remove JS script when you no longer use it (Optional - is by default).
- Uses CDN url instead of Embedded script (Optional - isn't by default).
در مطلب «طراحی افزونه پذیر با ASP.NET MVC 4.x/5.x - قسمت اول» با ساختار کلی یک پروژهی افزونهی پذیر ASP.NET MVC آشنا شدیم. پس از راه اندازی آن و مدتی کار کردن با این نوع پروژهها، این سؤال پیش خواهد آمد که ... خوب، اگر هر افزونه تصاویر یا فایلهای CSS و JS اختصاصی خودش را بخواهد داشته باشد، چطور؟ موارد عمومی مانند بوت استرپ و جیکوئری را میتوان در پروژهی پایه قرار داد تا تمام افزونهها به صورت یکسانی از آنها استفاده کنند، اما هدف، ماژولار شدن برنامه است و جدا کردن فایلهای ویژهی هر پروژه، از پروژهای دیگر و همچنین بالا بردن سهولت کار تیمی، با شکستن اجزای یک پروژه به صورت افزونههایی مختلف، بین اعضای یک تیم. در این قسمت نحوهی مدفون سازی انواع فایلهای استاتیک افزونهها را درون فایلهای DLL آنها بررسی خواهیم کرد. به این ترتیب دیگر نیازی به ارائهی مجزای آنها و یا کپی کردن آنها در پوشههای پروژهی اصلی نخواهد بود.
مدفون سازی فایلهای CSS و JS هر افزونه درون فایل DLL آن
به solution جاری، یک class library جدید را به نام MvcPluginMasterApp.Common اضافه کنید. از آن جهت قرار دادن کلاسهای عمومی و مشترک بین افزونهها استفاده خواهیم کرد. برای مثال قصد نداریم کلاسهای سفارشی و عمومی ذیل را هربار به صورت مستقیم در افزونهای جدید کپی کنیم. کتابخانهی Common، امکان استفادهی مجدد از یک سری کدهای تکراری را در بین افزونهها میسر میکند.
این پروژه برای کامپایل شدن نیاز به بستهی نیوگت ذیل دارد:
همچنین باید به صورت دستی، در قسمت ارجاعات پروژه، ارجاعی را به اسمبلی استاندارد System.Web نیز به آن اضافه نمائید.
پس از این مقدمات، کلاس ذیل را به این پروژهی class library جدید اضافه کنید:
اگر با سیستم bundling & minification کار کرده باشید، با تعاریفی مانند ("new Bundle("~/Plugin1/Scripts آشنا هستید. سازندهی کلاس Bundle، پارامتر دومی را نیز میپذیرد که از نوع IBundleTransform است. با پیاده سازی اینترفیس IBundleTransform میتوان محل ارائهی فایلهای استاتیک CSS و JS را بجای فایل سیستم متداول و پیش فرض، به منابع مدفون شدهی در اسمبلی جاری هدایت و تنظیم کرد.
کلاس فوق در اسمبلی معرفی شده به آن، توسط متد GetManifestResourceStream به دنبال فایلها و منابع مدفون شده گشته و سپس محتوای آنها را بازگشت میدهد.
اکنون برای استفادهی از آن، به پروژهی MvcPluginMasterApp.Plugin1 مراجعه کرده و ارجاعی را به پروژهی MvcPluginMasterApp.Common فوق اضافه نمائید. سپس در فایل Plugin1.cs، متد RegisterBundles آنرا به نحو ذیل تکمیل کنید:
در اینجا نحوهی کار با کلاس سفارشی EmbeddedResourceTransform را مشاهده میکنید.
ابتدا فایلهای js و سپس فایلهای css برنامه به سیستم Bundling برنامه اضافه شدهاند.
این فایلها به صورت ذیل در پروژه تعریف گردیدهاند:
همانطور که مشاهده میکنید، باید به خواص هر کدام مراجعه کرد و سپس Build action آنها را به embedded resource تغییر داد، تا در حین کامپایل، به صورت خودکار در قسمت منابع اسمبلی ذخیره شوند.
یک نکتهی مهم
اینبار برای مسیردهی منابع، باید بجای / فایل سیستم، از «نقطه» استفاده کرد. زیرا منابع با نامهایی مانند namespace.folder.name در قسمت resources یک اسمبلی ذخیره میشوند:
مدفون سازی تصاویر ثابت هر افزونه درون فایل DLL آن
مجددا به اسمبلی مشترک MvcPluginMasterApp.Common مراجعه کرده و اینبار کلاس جدید ذیل را به آن اضافه کنید:
تصاویر پروژهی افزونه نیز به صورت embedded resource در اسمبلی آن قرار خواهند گرفت. به همین جهت باید سیستم مسیریابی را پس درخواست رسیدهی جهت نمایش تصاویر، به منابع ذخیره شدهی در اسمبلی آن هدایت نمود. اینکار را با پیاده سازی یک IRouteHandler سفارشی، میتوان به نحو فوق مدیریت کرد.
این IRouteHandler، نام و پسوند فایل را دریافت کرده و سپس به قسمت منابع اسمبلی رجوع، فایل مرتبط را استخراج و سپس بازگشت میدهد. همچنین برای کاهش سربار سیستم، امکان کش شدن منابع استاتیک نیز در آن درنظر گرفته شدهاست و هدرهای خاص caching را به صورت خودکار اضافه میکند.
سیستم bundling نیز هدرهای کش کردن را به صورت خودکار و توکار اضافه میکند.
اکنون به تعاریف Plugin1 مراجعه کنید و سپس این IRouteHandler سفارشی را به نحو ذیل به آن معرفی نمائید:
در مسیریابی تعریف شده، تمام درخواستهای رسیدهی به مسیر NewsArea/Images به EmbeddedResourceRouteHandler هدایت میشوند.
مطابق تعریف آن، file و extension به صورت خودکار جدا شده و توسط routeData.Values در متد ProcessRequest کلاس EmbeddedResourceHttpHandler قابل دسترسی خواهند شد.
پسوندهایی که توسط آن بررسی میشوند از نوع png یا jpg تعریف شدهاند. همچنین مدت زمان کش کردن هر منبع استاتیک تصویری به یک ماه تنظیم شدهاست.
استفادهی نهایی از تنظیمات فوق در یک View افزونه
پس از اینکه تصاویر و فایلهای css و js را به صورت embedded resource تعریف کردیم و همچنین تنظیمات مسیریابی و bundling خاص آنها را نیز مشخص نمودیم، اکنون نوبت به استفادهی از آنها در یک View است:
در اینجا نحوهی تعریف فایلهای CSS و JS ارائه شدهی توسط سیستم Bundling را مشاهده میکنید.
همچنین مسیر تصویر مشخص شدهی در آن، اینبار یک NewsArea اضافهتر دارد. فایل اصلی تصویر، در مسیر Images/chart.png قرار گرفتهاست اما میخواهیم این درخواستها را به مسیریابی جدید {NewsArea/Images/{file}.{extension هدایت کنیم. بنابراین نیاز است به این نکته نیز دقت داشت.
اینبار اگر برنامه را اجرا کنیم، میتوان به سه نکته در آن دقت داشت:
الف) alert اجرا شده از فایل js مدفون شده خوانده شدهاست.
ب) رنگ قرمز متن (تگ h2) از فایل css مدفون شده، گرفته شدهاست.
ج) تصویر نمایش داده شده، همان تصویر مدفون شدهی در فایل DLL برنامه است.
و هیچکدام از این فایلها، به پوشههای پروژهی اصلی برنامه، کپی نشدهاند.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید:
MvcPluginMasterApp-Part2.zip
مدفون سازی فایلهای CSS و JS هر افزونه درون فایل DLL آن
به solution جاری، یک class library جدید را به نام MvcPluginMasterApp.Common اضافه کنید. از آن جهت قرار دادن کلاسهای عمومی و مشترک بین افزونهها استفاده خواهیم کرد. برای مثال قصد نداریم کلاسهای سفارشی و عمومی ذیل را هربار به صورت مستقیم در افزونهای جدید کپی کنیم. کتابخانهی Common، امکان استفادهی مجدد از یک سری کدهای تکراری را در بین افزونهها میسر میکند.
این پروژه برای کامپایل شدن نیاز به بستهی نیوگت ذیل دارد:
PM> install-package Microsoft.AspNet.Web.Optimization
پس از این مقدمات، کلاس ذیل را به این پروژهی class library جدید اضافه کنید:
using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; using System.Web.Optimization; namespace MvcPluginMasterApp.Common.WebToolkit { public class EmbeddedResourceTransform : IBundleTransform { private readonly IList<string> _resourceFiles; private readonly string _contentType; private readonly Assembly _assembly; public EmbeddedResourceTransform(IList<string> resourceFiles, string contentType, Assembly assembly) { _resourceFiles = resourceFiles; _contentType = contentType; _assembly = assembly; } public void Process(BundleContext context, BundleResponse response) { var result = new StringBuilder(); foreach (var resource in _resourceFiles) { using (var stream = _assembly.GetManifestResourceStream(resource)) { if (stream == null) { throw new KeyNotFoundException(string.Format("Embedded resource key: '{0}' not found in the '{1}' assembly.", resource, _assembly.FullName)); } using (var reader = new StreamReader(stream)) { result.Append(reader.ReadToEnd()); } } } response.ContentType = _contentType; response.Content = result.ToString(); } } }
کلاس فوق در اسمبلی معرفی شده به آن، توسط متد GetManifestResourceStream به دنبال فایلها و منابع مدفون شده گشته و سپس محتوای آنها را بازگشت میدهد.
اکنون برای استفادهی از آن، به پروژهی MvcPluginMasterApp.Plugin1 مراجعه کرده و ارجاعی را به پروژهی MvcPluginMasterApp.Common فوق اضافه نمائید. سپس در فایل Plugin1.cs، متد RegisterBundles آنرا به نحو ذیل تکمیل کنید:
namespace MvcPluginMasterApp.Plugin1 { public class Plugin1 : IPlugin { public EfBootstrapper GetEfBootstrapper() { return null; } public MenuItem GetMenuItem(RequestContext requestContext) { return new MenuItem { Name = "Plugin 1", Url = new UrlHelper(requestContext).Action("Index", "Home", new { area = "NewsArea" }) }; } public void RegisterBundles(BundleCollection bundles) { var executingAssembly = Assembly.GetExecutingAssembly(); // Mostly the default namespace and assembly name are the same var assemblyNameSpace = executingAssembly.GetName().Name; var scriptsBundle = new Bundle("~/Plugin1/Scripts", new EmbeddedResourceTransform(new List<string> { assemblyNameSpace + ".Scripts.test1.js" }, "application/javascript", executingAssembly)); if (!HttpContext.Current.IsDebuggingEnabled) { scriptsBundle.Transforms.Add(new JsMinify()); } bundles.Add(scriptsBundle); var cssBundle = new Bundle("~/Plugin1/Content", new EmbeddedResourceTransform(new List<string> { assemblyNameSpace + ".Content.test1.css" }, "text/css", executingAssembly)); if (!HttpContext.Current.IsDebuggingEnabled) { cssBundle.Transforms.Add(new CssMinify()); } bundles.Add(cssBundle); BundleTable.EnableOptimizations = true; } public void RegisterRoutes(RouteCollection routes) { } public void RegisterServices(IContainer container) { } } }
این فایلها به صورت ذیل در پروژه تعریف گردیدهاند:
همانطور که مشاهده میکنید، باید به خواص هر کدام مراجعه کرد و سپس Build action آنها را به embedded resource تغییر داد، تا در حین کامپایل، به صورت خودکار در قسمت منابع اسمبلی ذخیره شوند.
یک نکتهی مهم
اینبار برای مسیردهی منابع، باید بجای / فایل سیستم، از «نقطه» استفاده کرد. زیرا منابع با نامهایی مانند namespace.folder.name در قسمت resources یک اسمبلی ذخیره میشوند:
مدفون سازی تصاویر ثابت هر افزونه درون فایل DLL آن
مجددا به اسمبلی مشترک MvcPluginMasterApp.Common مراجعه کرده و اینبار کلاس جدید ذیل را به آن اضافه کنید:
using System; using System.Collections.Generic; using System.Reflection; using System.Web; using System.Web.Routing; namespace MvcPluginMasterApp.Common.WebToolkit { public class EmbeddedResourceRouteHandler : IRouteHandler { private readonly Assembly _assembly; private readonly string _resourcePath; private readonly TimeSpan _cacheDuration; public EmbeddedResourceRouteHandler(Assembly assembly, string resourcePath, TimeSpan cacheDuration) { _assembly = assembly; _resourcePath = resourcePath; _cacheDuration = cacheDuration; } IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) { return new EmbeddedResourceHttpHandler(requestContext.RouteData, _assembly, _resourcePath, _cacheDuration); } } public class EmbeddedResourceHttpHandler : IHttpHandler { private readonly RouteData _routeData; private readonly Assembly _assembly; private readonly string _resourcePath; private readonly TimeSpan _cacheDuration; public EmbeddedResourceHttpHandler( RouteData routeData, Assembly assembly, string resourcePath, TimeSpan cacheDuration) { _routeData = routeData; _assembly = assembly; _resourcePath = resourcePath; _cacheDuration = cacheDuration; } public bool IsReusable { get { return false; } } public void ProcessRequest(HttpContext context) { var routeDataValues = _routeData.Values; var fileName = routeDataValues["file"].ToString(); var fileExtension = routeDataValues["extension"].ToString(); var manifestResourceName = string.Format("{0}.{1}.{2}", _resourcePath, fileName, fileExtension); var stream = _assembly.GetManifestResourceStream(manifestResourceName); if (stream == null) { throw new KeyNotFoundException(string.Format("Embedded resource key: '{0}' not found in the '{1}' assembly.", manifestResourceName, _assembly.FullName)); } context.Response.Clear(); context.Response.ContentType = "application/octet-stream"; cacheIt(context.Response, _cacheDuration); stream.CopyTo(context.Response.OutputStream); } private static void cacheIt(HttpResponse response, TimeSpan duration) { var cache = response.Cache; var maxAgeField = cache.GetType().GetField("_maxAge", BindingFlags.Instance | BindingFlags.NonPublic); if (maxAgeField != null) maxAgeField.SetValue(cache, duration); cache.SetCacheability(HttpCacheability.Public); cache.SetExpires(DateTime.Now.Add(duration)); cache.SetMaxAge(duration); cache.AppendCacheExtension("must-revalidate, proxy-revalidate"); } } }
این IRouteHandler، نام و پسوند فایل را دریافت کرده و سپس به قسمت منابع اسمبلی رجوع، فایل مرتبط را استخراج و سپس بازگشت میدهد. همچنین برای کاهش سربار سیستم، امکان کش شدن منابع استاتیک نیز در آن درنظر گرفته شدهاست و هدرهای خاص caching را به صورت خودکار اضافه میکند.
سیستم bundling نیز هدرهای کش کردن را به صورت خودکار و توکار اضافه میکند.
اکنون به تعاریف Plugin1 مراجعه کنید و سپس این IRouteHandler سفارشی را به نحو ذیل به آن معرفی نمائید:
namespace MvcPluginMasterApp.Plugin1 { public class Plugin1 : IPlugin { public void RegisterRoutes(RouteCollection routes) { //todo: add custom routes. var assembly = Assembly.GetExecutingAssembly(); // Mostly the default namespace and assembly name are the same var nameSpace = assembly.GetName().Name; var resourcePath = string.Format("{0}.Images", nameSpace); routes.Insert(0, new Route("NewsArea/Images/{file}.{extension}", new RouteValueDictionary(new { }), new RouteValueDictionary(new { extension = "png|jpg" }), new EmbeddedResourceRouteHandler(assembly, resourcePath, cacheDuration: TimeSpan.FromDays(30)) )); } } }
مطابق تعریف آن، file و extension به صورت خودکار جدا شده و توسط routeData.Values در متد ProcessRequest کلاس EmbeddedResourceHttpHandler قابل دسترسی خواهند شد.
پسوندهایی که توسط آن بررسی میشوند از نوع png یا jpg تعریف شدهاند. همچنین مدت زمان کش کردن هر منبع استاتیک تصویری به یک ماه تنظیم شدهاست.
استفادهی نهایی از تنظیمات فوق در یک View افزونه
پس از اینکه تصاویر و فایلهای css و js را به صورت embedded resource تعریف کردیم و همچنین تنظیمات مسیریابی و bundling خاص آنها را نیز مشخص نمودیم، اکنون نوبت به استفادهی از آنها در یک View است:
@{ ViewBag.Title = "From Plugin 1"; } @Styles.Render("~/Plugin1/Content") <h2>@ViewBag.Message</h2> <div class="row"> Embedded image: <img src="@Url.Content("~/NewsArea/Images/chart.png")" alt="clock" /> </div> @section scripts { @Scripts.Render("~/Plugin1/Scripts") }
همچنین مسیر تصویر مشخص شدهی در آن، اینبار یک NewsArea اضافهتر دارد. فایل اصلی تصویر، در مسیر Images/chart.png قرار گرفتهاست اما میخواهیم این درخواستها را به مسیریابی جدید {NewsArea/Images/{file}.{extension هدایت کنیم. بنابراین نیاز است به این نکته نیز دقت داشت.
اینبار اگر برنامه را اجرا کنیم، میتوان به سه نکته در آن دقت داشت:
الف) alert اجرا شده از فایل js مدفون شده خوانده شدهاست.
ب) رنگ قرمز متن (تگ h2) از فایل css مدفون شده، گرفته شدهاست.
ج) تصویر نمایش داده شده، همان تصویر مدفون شدهی در فایل DLL برنامه است.
و هیچکدام از این فایلها، به پوشههای پروژهی اصلی برنامه، کپی نشدهاند.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید:
MvcPluginMasterApp-Part2.zip
صورت مساله:
- میخواهیم footer پیش فرض PdfReport را که تاریخ را در یک سمت، و شماره صفحه را در سمتی دیگر نمایش میدهد، به عبارت «صفحه x از n» تغییر دهیم.
- میخواهیم در Header گزارش بجای Header پیش فرض PdfReport یکی از قالبهای PDF تهیه شده توسط Open Office را نمایش دهیم (و یا هر ساختار دیگری را).
تمام اجزای PdfReport جهت امکان اعمال تغییرات کلی و توسعه آنها طراحی شدهاند؛ قالبها، هدر، فوتر، منابع داده، قالبهای نمایش سلولها، تعریف توابع تجمعی سفارشی و غیره. جهت سهولت کار، به ازای هر یک از این موارد، پیاده سازیهای پیش فرضی در PdfReport قرار دارند، امکان اگر مورد رضایت شما نیستند ... از بنیان تغییرشان دهید! (و همچنین اگر مورد جالبی را پیاده سازی کردید، میتوانید به عنوان یک وصله جدید ارائه دهید تا به پروژه اضافه شود)
ضمنا این مطالب سفارشی سازی نیاز به آشنایی با ساختار iTextSharp را نیز دارند؛ در حد ایجاد یک جدول ساده باید با iTextSharp آشنا باشید.
مدلهای مورد استفاده:
توسط این مدلها قصد داریم تعدادی فعالیت (Task) را که به تعدادی کاربر انتساب یافته است، نمایش دهیم. همچنین نمایش مقادیر خواص تو در تو نیز در اینجا مد نظر است؛ برای مثال ستونی مانند این:
کدهای کامل مثال را در ادامه ملاحظه خواهید نمود:
به همراه Header سفارشی:
و Footer سفارشی استفاده شده:
البته لازم به ذکر است که تمام این کدها به پوشه Samples سورس پروژه نیز جهت سهولت دسترسی، اضافه شدهاند .
توضیحات:
برای پیاده سازی Header و Footer سفارشی در PdfReport نیاز خواهید داشت تا دو اینترفیس IPageHeader و IPageFooter را پیاده سازی کنید.
ساختار IPageHeader را در ذیل ملاحظه میکنید:
RenderingGroupHeader مرتبط است به مباحث گروه بندی اطلاعات و گزارشات master-detail که در قسمتهای بعد به آنها اشاره خواهد شد. چون در اینجا به آن نیازی نداشتیم، تنها کافی است متد متناظر با آن، null بر گرداند که در کلاس CustomHeader فوق قابل مشاهده است.
متد RenderingReportHeader به ازای تولید هر صفحه جدید، فراخوانی خواهد شد. به عبارتی میتوانید در صفحات مختلف، هدرهای مختلفی را نمایش دهید.
خروجی هر دو متد در اینجا یک جدول از نوع PdfPTable است. بنابراین هر نوع ساختار دلخواهی را که علاقمند هستید به شکل یک PdfPTable ایجاد کرده و بازگشت دهید. این جدول در هدر صفحات ظاهر خواهد شد.
برای نمونه در کلاس CustomHeader، یک قالب تهیه شده توسط Open Office توسط متد توکار PdfImageHelper.GetITextSharpImageFromPdfTemplate دریافت و تبدیل به تصویر میشود. این تصویر از نوع تصاویر قابل درک توسط iTextSharp است و نه اینکه واقعا تبدیل به یک تصویر معمولی مثلا از نوع bmp شود. سپس این تصویر، در یک ردیف از جدولی قرار داده شده و این جدول بازگشت داده میشود.
در کل یا توسط کار با PdfPTable میتوانید یک هدر غیرپیش فرض را طراحی کنید و یا میتوانید توسط ابزارهای بصری مانند Open Office یک قالب خاص را برای آن تهیه کرده و به روشی که ذکر شد و کدهای آنرا ملاحظه میکنید، بارگذاری و استفاده کنید. این قالبها در مسیر Bin\Data سورسهای پروژه قرار داده شدهاند.
ساختار IPageFooter به صورت زیر است:
برای طراحی یک Footer سفارشی کافی است اینترفیس فوق را پیاده سازی کنید که نمونهای از آنرا در کدهای کلاس CustomFooter ملاحظه مینمائید.
متد DocumentOpened، با وهله سازی شیء Document فراخوانی میشود.
متد PageFinished هر بار پیش از اتمام کار صفحه جاری و افزوده شدن آن به Document فراخوانی میگردد.
متد ClosingDocument، در زمان بسته شدن شیء Document فراخوانی خواهد شد.
اگر به امضای این متدها دقت کنید، شیء PdfWriter در اختیار شما قرار گرفته است که توسط آن میتوان مستقیما بر روی فایل PDF، محتوایی را قرار داد. شیء Document نیز در دسترس است. مثلا توسط آن میتوان اندازه دقیق صفحه را بدست آورد.
به علاوه پارامتر columnCellsSummaryData نیز امکان دسترسی به مقادیر ردیفهای قبلی را در اختیار شما قرار میدهد. برای مثال اگر نیاز دارید تا بر اساس مقادیر ستونها و ردیفهای قبلی، محاسباتی را انجام داده و در پایین صفحات درج کنید، به این ترتیب دسترسی کاملی به آنها، خواهید داشت.
استفاده از این کلاسهای سفارشی نیز همواره به شکل زیر خواهد بود:
کلا در PdfReport هر جایی متدی به نام CustomXYZ را مشاهده کردید، این متد یک اینترفیس را دریافت میکند. به عبارتی این امکان را خواهید داشت تا از متدهای پیش فرض مهیا صرفنظر کرده و مطابق نیاز، نسبت به پیاده سازی و استفاده از وهله جدیدی از این اینترفیس تعریف شده، اقدام کنید.
- میخواهیم footer پیش فرض PdfReport را که تاریخ را در یک سمت، و شماره صفحه را در سمتی دیگر نمایش میدهد، به عبارت «صفحه x از n» تغییر دهیم.
- میخواهیم در Header گزارش بجای Header پیش فرض PdfReport یکی از قالبهای PDF تهیه شده توسط Open Office را نمایش دهیم (و یا هر ساختار دیگری را).
تمام اجزای PdfReport جهت امکان اعمال تغییرات کلی و توسعه آنها طراحی شدهاند؛ قالبها، هدر، فوتر، منابع داده، قالبهای نمایش سلولها، تعریف توابع تجمعی سفارشی و غیره. جهت سهولت کار، به ازای هر یک از این موارد، پیاده سازیهای پیش فرضی در PdfReport قرار دارند، امکان اگر مورد رضایت شما نیستند ... از بنیان تغییرشان دهید! (و همچنین اگر مورد جالبی را پیاده سازی کردید، میتوانید به عنوان یک وصله جدید ارائه دهید تا به پروژه اضافه شود)
ضمنا این مطالب سفارشی سازی نیاز به آشنایی با ساختار iTextSharp را نیز دارند؛ در حد ایجاد یک جدول ساده باید با iTextSharp آشنا باشید.
مدلهای مورد استفاده:
namespace PdfReportSamples.Models { public class Task { public int Id { set; get; } public string Name { set; get; } public int PercentCompleted { set; get; } public bool IsActive { set; get; } public User Assignee { set; get; } } }
using System; namespace PdfReportSamples.Models { public class User { public int Id { set; get; } public string Name { set; get; } public string LastName { set; get; } public long Balance { set; get; } public DateTime RegisterDate { set; get; } } }
column.PropertyName<Task>(x => x.Assignee.Name)
using System; using System.Collections.Generic; using System.Drawing; using PdfReportSamples.Models; using PdfRpt.Core.Contracts; using PdfRpt.FluentInterface; namespace PdfReportSamples.CustomHeaderFooter { public class CustomHeaderFooterPdfReport { readonly CustomHeader _customHeader = new CustomHeader(); public IPdfReportData CreatePdfReport() { return new PdfReport().DocumentPreferences(doc => { doc.RunDirection(PdfRunDirection.LeftToRight); 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(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf", Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf"); }) .PagesFooter(footer => { footer.CustomFooter(new CustomFooter(footer.PdfFont, PdfRunDirection.LeftToRight)); }) .PagesHeader(header => { header.CustomHeader(_customHeader); }) .MainTableTemplate(template => { template.BasicTemplate(BasicTemplate.SilverTemplate); }) .MainTablePreferences(table => { table.ColumnsWidthsType(TableColumnWidthType.Relative); table.MultipleColumnsPerPage(new MultipleColumnsPerPage { ColumnsGap = 22, ColumnsPerPage = 2, ColumnsWidth = 250, IsRightToLeft = false, TopMargin = 7 }); }) .MainTableDataSource(dataSource => { var rows = new List<Task>(); var rnd = new Random(); for (int i = 1; i < 210; i++) { rows.Add(new Task { Assignee = new User { Id = i, Name = "user-" + i }, IsActive = rnd.Next(0, 2) == 1 ? true : false, Name = "task-" + i }); } dataSource.StronglyTypedList(rows); }) .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("#"); }); columns.AddColumn(column => { column.PropertyName<Task>(x => x.Name); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(1); column.Width(3); column.HeaderCell("Task Name"); }); columns.AddColumn(column => { column.PropertyName<Task>(x => x.Assignee.Name); // nested property support column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(2); column.Width(3); column.HeaderCell("Assignee"); }); columns.AddColumn(column => { column.PropertyName<Task>(x => x.IsActive); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(3); column.Width(2); column.HeaderCell("Active"); column.ColumnItemsTemplate(template => { template.Checkmark(checkmarkFillColor: Color.Green, crossSignFillColor: Color.DarkRed); }); }); }) .MainTableEvents(events => { events.DataSourceIsEmpty(message: "There is no data available to display."); }) .Export(export => { export.ToExcel(); }) .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\CustomHeaderFooterPdfReportSample.pdf")); } } }
به همراه Header سفارشی:
using System.Collections.Generic; using iTextSharp.text; using iTextSharp.text.pdf; using PdfRpt.Core.Contracts; using PdfRpt.Core.Helper; namespace PdfReportSamples.CustomHeaderFooter { public class CustomHeader : IPageHeader { public PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> rowdata, IList<SummaryCellData> summaryData) { return null; } Image _image; public PdfPTable RenderingReportHeader(Document pdfDoc, PdfWriter pdfWriter, IList<SummaryCellData> summaryData) { if (_image == null) //cache is empty { var templatePath = AppPath.ApplicationPath + "\\data\\PdfHeaderTemplate.pdf"; _image = PdfImageHelper.GetITextSharpImageFromPdfTemplate(pdfWriter, templatePath); } var table = new PdfPTable(1); var cell = new PdfPCell(_image, true) { Border = 0 }; table.AddCell(cell); return table; } } }
و Footer سفارشی استفاده شده:
using System.Collections.Generic; using iTextSharp.text; using iTextSharp.text.pdf; using PdfRpt.Core.Contracts; namespace PdfReportSamples.CustomHeaderFooter { public class CustomFooter : IPageFooter { PdfContentByte _pdfContentByte; readonly IPdfFont _pdfRptFont; readonly Font _font; readonly PdfRunDirection _direction; PdfTemplate _template; public CustomFooter(IPdfFont pdfRptFont, PdfRunDirection direction) { _direction = direction; _pdfRptFont = pdfRptFont; _font = _pdfRptFont.Fonts[0]; } public void ClosingDocument(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData) { _template.BeginText(); _template.SetFontAndSize(_pdfRptFont.Fonts[0].BaseFont, 8); _template.SetTextMatrix(0, 0); _template.ShowText((writer.PageNumber - 1).ToString()); _template.EndText(); } public void PageFinished(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData) { var pageSize = document.PageSize; var text = "Page " + writer.PageNumber + " / "; var textLen = _font.BaseFont.GetWidthPoint(text, _font.Size); var center = (pageSize.Left + pageSize.Right) / 2; var align = _direction == PdfRunDirection.RightToLeft ? Element.ALIGN_RIGHT : Element.ALIGN_LEFT; ColumnText.ShowTextAligned( canvas: _pdfContentByte, alignment: align, phrase: new Phrase(text, _font), x: center, y: pageSize.GetBottom(25), rotation: 0, runDirection: (int)_direction, arabicOptions: 0); var x = _direction == PdfRunDirection.RightToLeft ? center - textLen : center + textLen; _pdfContentByte.AddTemplate(_template, x, pageSize.GetBottom(25)); } public void DocumentOpened(PdfWriter writer, IList<SummaryCellData> columnCellsSummaryData) { _pdfContentByte = writer.DirectContent; _template = _pdfContentByte.CreateTemplate(50, 50); } } }
البته لازم به ذکر است که تمام این کدها به پوشه Samples سورس پروژه نیز جهت سهولت دسترسی، اضافه شدهاند .
توضیحات:
برای پیاده سازی Header و Footer سفارشی در PdfReport نیاز خواهید داشت تا دو اینترفیس IPageHeader و IPageFooter را پیاده سازی کنید.
ساختار IPageHeader را در ذیل ملاحظه میکنید:
using System.Collections.Generic; using iTextSharp.text; using iTextSharp.text.pdf; namespace PdfRpt.Core.Contracts { public interface IPageHeader { PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> newGroupInfo, IList<SummaryCellData> summaryData); PdfPTable RenderingReportHeader(Document pdfDoc, PdfWriter pdfWriter, IList<SummaryCellData> summaryData); } }
RenderingGroupHeader مرتبط است به مباحث گروه بندی اطلاعات و گزارشات master-detail که در قسمتهای بعد به آنها اشاره خواهد شد. چون در اینجا به آن نیازی نداشتیم، تنها کافی است متد متناظر با آن، null بر گرداند که در کلاس CustomHeader فوق قابل مشاهده است.
متد RenderingReportHeader به ازای تولید هر صفحه جدید، فراخوانی خواهد شد. به عبارتی میتوانید در صفحات مختلف، هدرهای مختلفی را نمایش دهید.
خروجی هر دو متد در اینجا یک جدول از نوع PdfPTable است. بنابراین هر نوع ساختار دلخواهی را که علاقمند هستید به شکل یک PdfPTable ایجاد کرده و بازگشت دهید. این جدول در هدر صفحات ظاهر خواهد شد.
برای نمونه در کلاس CustomHeader، یک قالب تهیه شده توسط Open Office توسط متد توکار PdfImageHelper.GetITextSharpImageFromPdfTemplate دریافت و تبدیل به تصویر میشود. این تصویر از نوع تصاویر قابل درک توسط iTextSharp است و نه اینکه واقعا تبدیل به یک تصویر معمولی مثلا از نوع bmp شود. سپس این تصویر، در یک ردیف از جدولی قرار داده شده و این جدول بازگشت داده میشود.
در کل یا توسط کار با PdfPTable میتوانید یک هدر غیرپیش فرض را طراحی کنید و یا میتوانید توسط ابزارهای بصری مانند Open Office یک قالب خاص را برای آن تهیه کرده و به روشی که ذکر شد و کدهای آنرا ملاحظه میکنید، بارگذاری و استفاده کنید. این قالبها در مسیر Bin\Data سورسهای پروژه قرار داده شدهاند.
ساختار IPageFooter به صورت زیر است:
using iTextSharp.text; using iTextSharp.text.pdf; using System.Collections.Generic; namespace PdfRpt.Core.Contracts { public interface IPageFooter { void DocumentOpened(PdfWriter writer, IList<SummaryCellData> columnCellsSummaryData); void PageFinished(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData); void ClosingDocument(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData); } }
برای طراحی یک Footer سفارشی کافی است اینترفیس فوق را پیاده سازی کنید که نمونهای از آنرا در کدهای کلاس CustomFooter ملاحظه مینمائید.
متد DocumentOpened، با وهله سازی شیء Document فراخوانی میشود.
متد PageFinished هر بار پیش از اتمام کار صفحه جاری و افزوده شدن آن به Document فراخوانی میگردد.
متد ClosingDocument، در زمان بسته شدن شیء Document فراخوانی خواهد شد.
اگر به امضای این متدها دقت کنید، شیء PdfWriter در اختیار شما قرار گرفته است که توسط آن میتوان مستقیما بر روی فایل PDF، محتوایی را قرار داد. شیء Document نیز در دسترس است. مثلا توسط آن میتوان اندازه دقیق صفحه را بدست آورد.
به علاوه پارامتر columnCellsSummaryData نیز امکان دسترسی به مقادیر ردیفهای قبلی را در اختیار شما قرار میدهد. برای مثال اگر نیاز دارید تا بر اساس مقادیر ستونها و ردیفهای قبلی، محاسباتی را انجام داده و در پایین صفحات درج کنید، به این ترتیب دسترسی کاملی به آنها، خواهید داشت.
استفاده از این کلاسهای سفارشی نیز همواره به شکل زیر خواهد بود:
readonly CustomHeader _customHeader = new CustomHeader(); //... .PagesFooter(footer => { footer.CustomFooter(new CustomFooter(footer.PdfFont, PdfRunDirection.LeftToRight)); }) .PagesHeader(header => { header.CustomHeader(_customHeader); })
در قسمت اول مشاهده کردیم که به چه صورت میتوان از دکمههای CSS ایی بجای دکمههای معمولی در ASP.NET MVC استفاده کنیم. در این قسمت قصد داریم قابلیت مهم زیر را نیز به این تغییر اضافه نمائیم:
کاربر امکان دوبار کلیک کردن همزمان را بر روی دکمه ارسال، نداشته باشد
انجام اینکار با jQuery بسیار ساده است:
به این ترتیب کاربر امکان کلیک مجدد را بر روی دکمه ارسال، از دست خواهد داد.
و اینکار ... یک مشکل را اضافه میکند:
اگر نتیجه اعتبار سنجی سمت کاربر فرم، تکمیل نشده باشد، کاربر پس از رفع مشکل دیگر نمیتواند فرم را ارسال کند. به همین جهت باید به نحوی اطمینان حاصل کرد که آیا اعتبارسنجی موفقیت آمیز بوده یا خیر؛ سپس دکمه را غیرفعال کنیم.
برای پیاده سازی این امر باید jQuery Validator همراه با ASP.NET MVC را به صورت دستی فراخوانی کرده و نتیجه آنرا ارزیابی کنیم:
در متد جاوا اسکریپتی validateThisForm، سیستم jQuery Validator به صورت دستی فراخوانی شده و سپس نتیجه عملیات بازگشت داده میشود.
در متد customSubmit، ابتدا وضعیت اعتبار سنجی فرم بررسی شده و سپس بر این اساس تصمیم گرفته خواهد شد که آیا باید دکمه غیرفعال شود و سپس فرم ارسال گردد یا خیر.
یک مثال از استفاده آن:
دریافت کدهای کامل این مثال:
MvcApplication27.7z
کاربر امکان دوبار کلیک کردن همزمان را بر روی دکمه ارسال، نداشته باشد
انجام اینکار با jQuery بسیار ساده است:
$(el).prop("onclick", null).attr("onclick", null);
و اینکار ... یک مشکل را اضافه میکند:
اگر نتیجه اعتبار سنجی سمت کاربر فرم، تکمیل نشده باشد، کاربر پس از رفع مشکل دیگر نمیتواند فرم را ارسال کند. به همین جهت باید به نحوی اطمینان حاصل کرد که آیا اعتبارسنجی موفقیت آمیز بوده یا خیر؛ سپس دکمه را غیرفعال کنیم.
برای پیاده سازی این امر باید jQuery Validator همراه با ASP.NET MVC را به صورت دستی فراخوانی کرده و نتیجه آنرا ارزیابی کنیم:
function validateThisForm(formId) { var val = $(formId).validate(); val.form(); return val.valid(); } function customSubmit(el, formId) { if (!validateThisForm(formId)) return; $(el).prop("onclick", null).attr("onclick", null); $(formId).submit(); }
در متد customSubmit، ابتدا وضعیت اعتبار سنجی فرم بررسی شده و سپس بر این اساس تصمیم گرفته خواهد شد که آیا باید دکمه غیرفعال شود و سپس فرم ارسال گردد یا خیر.
یک مثال از استفاده آن:
@model MvcApplication27.Models.User @{ ViewBag.Title = "Index"; } <h2> Index</h2> @using (Html.BeginForm(Html.BeginForm("Index", "Home", FormMethod.Post, new { id = "MyForm" }))) { @Html.ValidationSummary(true) <fieldset> <legend>User</legend> <div class="editor-label"> @Html.LabelFor(model => model.Name) </div> <div class="editor-field"> @Html.EditorFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name) </div> <span style="margin-top:7px;" onclick="customSubmit(this, '#MyForm')" class="btn btn-info">ارسال</span> </fieldset> }
MvcApplication27.7z