فقط به خاطر یک نیم فاصله!
اگر با استاندارد 2091 میخواهید کار کنید، بله.
در غیر اینصورت (نیاز به استفاده از استاندارد 9147)، فایلهای آقای اخگری را دریافت کنید که نسخهی 64 بیتی هم دارد و بدون مشکل با ویندوز 7 64 بیتی کار میکند.
نیاز به علامتگذاری خواصی که باید رمزنگاری شوند
میخواهیم خاصیت یا خاصیتهای مشخصی، از یک مدل را رمزنگاری شده به سمت کلاینت ارسال کنیم. به همین جهت ویژگی خالی زیر را به پروژه اضافه میکنیم تا از آن تنها جهت علامتگذاری این نوع خواص، استفاده کنیم:
using System; namespace EncryptedModelBinder.Utils { [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class EncryptedFieldAttribute : Attribute { } }
رمزنگاری خودکار مدل خروجی از یک اکشن متد
در ادامه کدهای کامل یک ResultFilter را مشاهده میکنید که مدل ارسالی به سمت کلاینت را یافته و سپس خواصی از آنرا که با ویژگی EncryptedField مزین شدهاند، به صورت خودکار رمزنگاری میکند:
namespace EncryptedModelBinder.Utils { public class EncryptedFieldResultFilter : ResultFilterAttribute { private readonly IProtectionProviderService _protectionProviderService; private readonly ILogger<EncryptedFieldResultFilter> _logger; private readonly ConcurrentDictionary<Type, bool> _modelsWithEncryptedFieldAttributes = new ConcurrentDictionary<Type, bool>(); public EncryptedFieldResultFilter( IProtectionProviderService protectionProviderService, ILogger<EncryptedFieldResultFilter> logger) { _protectionProviderService = protectionProviderService; _logger = logger; } public override void OnResultExecuting(ResultExecutingContext context) { var model = context.Result switch { PageResult pageResult => pageResult.Model, // For Razor pages ViewResult viewResult => viewResult.Model, // For MVC Views ObjectResult objectResult => objectResult.Value, // For Web API results _ => null }; if (model is null) { return; } if (typeof(IEnumerable).IsAssignableFrom(model.GetType())) { foreach (var item in model as IEnumerable) { encryptProperties(item); } } else { encryptProperties(model); } } private void encryptProperties(object model) { var modelType = model.GetType(); if (_modelsWithEncryptedFieldAttributes.TryGetValue(modelType, out var hasEncryptedFieldAttribute) && !hasEncryptedFieldAttribute) { return; } foreach (var property in modelType.GetProperties()) { var attribute = property.GetCustomAttributes(typeof(EncryptedFieldAttribute), false).FirstOrDefault(); if (attribute == null) { continue; } hasEncryptedFieldAttribute = true; var value = property.GetValue(model); if (value is null) { continue; } if (value.GetType() != typeof(string)) { _logger.LogWarning($"[EncryptedField] should be applied to `string` proprties, But type of `{property.DeclaringType}.{property.Name}` is `{property.PropertyType}`."); continue; } var encryptedData = _protectionProviderService.Encrypt(value.ToString()); property.SetValue(model, encryptedData); } _modelsWithEncryptedFieldAttributes.TryAdd(modelType, hasEncryptedFieldAttribute); } } }
- در اینجا برای رمزنگاری از IProtectionProviderService استفاده شدهاست که در بستهی DNTCommon.Web.Core تعریف شدهاست. این سرویس در پشت صحنه از سیستم Data Protection استفاده میکند.
- سپس رخداد OnResultExecuting، بازنویسی شدهاست تا بتوان به مدل ارسالی به سمت کلاینت، پیش از ارسال نهایی آن، دسترسی یافت.
- context.Result میتواند از نوع PageResult صفحات Razor باشد و یا از نوع ViewResult مدلهای متداول Viewهای پروژههای MVC و یا از نوع ObjectResult که مرتبط است به پروژههای Web Api بدون هیچ نوع View سمت سروری. هر کدام از این نوعها، دارای خاصیت مدل هستند که در اینجا قصد بررسی آنرا داریم.
- پس از مشخص شدن شیء Model، اکنون حلقهای را بر روی خواص آن تشکیل داده و خواصی را که دارای ویژگی EncryptedFieldAttribute هستند، یافته و آنها را رمزنگاری میکنیم.
روش اعمال این فیلتر باید به صورت سراسری باشد:
namespace EncryptedModelBinder { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddDNTCommonWeb(); services.AddControllersWithViews(options => { options.Filters.Add(typeof(EncryptedFieldResultFilter)); }); }
رمزگشایی خودکار مدل دریافتی از سمت کلاینت
تا اینجا موفق شدیم خواص ویژهای از مدلها را رمزنگاری کنیم. مرحلهی بعد، رمزگشایی خودکار این اطلاعات در سمت سرور است. به همین جهت نیاز داریم تا در سیستم Model Binding پیشفرض ASP.NET Core مداخله کرده و منطق سفارشی خود را تزریق کنیم. بنابراین در ابتدا یک IModelBinderProvider سفارشی را تهیه میکنیم تا در صورتیکه خاصیت جاری در حال بررسی توسط سیستم Model Binding دارای ویژگی EncryptedFieldAttribute بود، از EncryptedFieldModelBinder برای پردازش آن استفاده کند:
namespace EncryptedModelBinder.Utils { public class EncryptedFieldModelBinderProvider : IModelBinderProvider { public IModelBinder GetBinder(ModelBinderProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (context.Metadata.IsComplexType) { return null; } var propName = context.Metadata.PropertyName; if (string.IsNullOrWhiteSpace(propName)) { return null; } var propInfo = context.Metadata.ContainerType.GetProperty(propName); if (propInfo == null) { return null; } var attribute = propInfo.GetCustomAttributes(typeof(EncryptedFieldAttribute), false).FirstOrDefault(); if (attribute == null) { return null; } return new BinderTypeModelBinder(typeof(EncryptedFieldModelBinder)); } } }
namespace EncryptedModelBinder.Utils { public class EncryptedFieldModelBinder : IModelBinder { private readonly IProtectionProviderService _protectionProviderService; public EncryptedFieldModelBinder(IProtectionProviderService protectionProviderService) { _protectionProviderService = protectionProviderService; } public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } var logger = bindingContext.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>(); var fallbackBinder = new SimpleTypeModelBinder(bindingContext.ModelType, logger); var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (valueProviderResult == ValueProviderResult.None) { return fallbackBinder.BindModelAsync(bindingContext); } bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); var valueAsString = valueProviderResult.FirstValue; if (string.IsNullOrWhiteSpace(valueAsString)) { return fallbackBinder.BindModelAsync(bindingContext); } var decryptedResult = _protectionProviderService.Decrypt(valueAsString); bindingContext.Result = ModelBindingResult.Success(decryptedResult); return Task.CompletedTask; } } }
پس از این تعاریف نیاز است EncryptedFieldModelBinderProvider را به صورت زیر به سیستم معرفی کرد:
namespace EncryptedModelBinder { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddDNTCommonWeb(); services.AddControllersWithViews(options => { options.ModelBinderProviders.Insert(0, new EncryptedFieldModelBinderProvider()); options.Filters.Add(typeof(EncryptedFieldResultFilter)); }); }
یک مثال
فرض کنید مدلهای زیر تعریف شدهاند:
namespace EncryptedModelBinder.Models { public class ProductInputModel { [EncryptedField] public string Id { get; set; } [EncryptedField] public int Price { get; set; } public string Name { get; set; } } } namespace EncryptedModelBinder.Models { public class ProductViewModel { [EncryptedField] public string Id { get; set; } [EncryptedField] public int Price { get; set; } public string Name { get; set; } } }
اکنون کنترلر زیر زمانیکه رندر شود، View متناظر با اکشن متد Index آن، یکسری لینک را به اکشن متد Details، جهت مشاهدهی جزئیات محصول، تولید میکند. همچنین اکشن متد Products آن هم فقط یک خروجی JSON را به همراه دارد:
namespace EncryptedModelBinder.Controllers { public class HomeController : Controller { public IActionResult Index() { var model = getProducts(); return View(model); } public ActionResult<string> Details(ProductInputModel model) { return model.Id; } public ActionResult<List<ProductViewModel>> Products() { return getProducts(); } private static List<ProductViewModel> getProducts() { return new List<ProductViewModel> { new ProductViewModel { Id = "1", Name = "Product 1"}, new ProductViewModel { Id = "2", Name = "Product 2"}, new ProductViewModel { Id = "3", Name = "Product 3"} }; } } }
@model List<ProductViewModel> <h3>Home</h3> <ul> @foreach (var item in Model) { <li><a asp-action="Details" asp-route-id="@item.Id">@item.Name</a></li> } </ul>
و اگر یکی از لینکها را درخواست کنیم، خروجی model.Id، به صورت معمولی و رمزگشایی شدهای مشاهده میشود (این خروجی یک رشتهاست که هیچ ویژگی خاصی به آن اعمال نشدهاست. به همین جهت، اینبار این خروجی معمولی مشاهده میشود). هدف از اکشن متد Details، نمایش رمزگشایی خودکار اطلاعات است.
و یا اگر اکشن متدی که همانند اکشن متدهای Web API، فقط یک شیء JSON را باز میگرداند، فراخوانی کنیم نیز میتوان به خروجی رمزنگاری شدهی زیر رسید:
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: EncryptedModelBinder.zip
نگاشت خودکار مجموعهها در Fluent NHibernate ساده است و نیاز به تنظیم خاصی ندارد. برای مثال IList به صورت خودکار به Bag ترجمه میشود و الی آخر.
البته شاید سؤال بپرسید که این Bag از کجا آمده؟ کلا 6 نوع مجموعه در NHibernate پشتیبانی میشوند که شامل Array، Primitive-Array ، Bag ، Set ، List و Map هستند؛ این اسامی هم جهت حفظ سازگاری با جاوا تغییر نکردهاند و گرنه معادلهای آنها در دات نت به این شرح هستند:
Bag=IList
Set=Iesi.Collections.ISet
List=IList
Map=IDictionary
البته در دات نت 4 ، ISet هم به صورت توکار اضافه شده، اما NHibernate از مدتها قبل آنرا از کتابخانهی Iesi.Collections به عاریت گرفته است. مهمترین تفاوتهای این مجموعهها هم در پذیرفتن یا عدم پذیرش اعضای تکراری است. Set و Map اعضای تکراری نمیپذیرند.
در ادامه میخواهیم طرز کار با Map یا همان IDictionary دات نت را بررسی کنیم:
الف) حالتی که نوع کلید و مقدار (در یک عضو Dictionary تعریف شده)، Entity نیستند
using System.Collections.Generic;
namespace Test1.Model12
{
public class User
{
public virtual int Id { set; get; }
public virtual string Name { get; set; }
public virtual IDictionary<string, string> Preferences { get; set; }
}
}
نحوه تعریف نگاشت که مبتنی است بر مشخص سازی تعاریف کلید و مقدار آن جهت تشکیل یک Map یا همان Dictionary :
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;
namespace Test1.Model12
{
public class UserMapping : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Id(x => x.Id);
mapping.HasMany(x => x.Preferences)
.AsMap<string>("FieldKey")
.Element("FieldValue", x => x.Type<string>().Length(500));
}
}
}
خروجی SQL متناظر:
create table "User" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)
create table Preferences (
User_id INT not null,
FieldValue NVARCHAR(500) null,
FieldKey NVARCHAR(255) not null,
primary key (User_id, FieldKey)
)
alter table Preferences
add constraint FKD6CB18523B1FD789
foreign key (User_id)
references "User"
ب) حالتی که مقدار، Entity است
using System.Collections.Generic;
namespace Test1.Model13
{
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IDictionary<string, Property> Properties { get; set; }
}
public class Property
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Value { get; set; }
public virtual User User { get; set; }
}
}
نحوه تعریف نگاشت:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;
namespace Test1.Model13
{
public class UserMapping : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Id(x => x.Id);
mapping.HasMany<Property>(x => x.Properties)
.AsMap<string>("FieldKey")
.Component(x => x.Map(c => c.Id));
}
}
}
create table "Property" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
Value NVARCHAR(255) null,
User_id INT null,
primary key (Id)
)
create table "User" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)
create table Properties (
User_id INT not null,
Id INT null,
FieldKey NVARCHAR(255) not null,
primary key (User_id, FieldKey)
)
alter table "Property"
add constraint FKF9F4D85A3B1FD7A2
foreign key (User_id)
references "User"
alter table Properties
add constraint FK63646D853B1FD7A2
foreign key (User_id)
references "User"
ج) حالتی که کلید، Entity است
using System;
using System.Collections.Generic;
namespace Test1.Model14
{
public class FormData
{
public virtual int Id { get; set; }
public virtual DateTime? DateTime { get; set; }
public virtual IDictionary<FormField, string> FormPropertyValues { get; set; }
}
public class FormField
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
}
نحوه تعریف نگاشت:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;
namespace Test1.Model14
{
public class FormDataMapping : IAutoMappingOverride<FormData>
{
public void Override(AutoMapping<FormData> mapping)
{
mapping.Id(x => x.Id);
mapping.HasMany<FormField>(x => x.FormPropertyValues)
.AsEntityMap("FieldId")
.Element("FieldValue", x => x.Type<string>().Length(500))
.Cascade.All();
}
}
}
create table "FormData" (
Id INT IDENTITY NOT NULL,
DateTime DATETIME null,
primary key (Id)
)
create table FormPropertyValues (
FormData_id INT not null,
FieldValue NVARCHAR(500) null,
FieldId INT not null,
primary key (FormData_id, FieldId)
)
create table "FormField" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)
alter table FormPropertyValues
add constraint FKB807B9C090849E
foreign key (FormData_id)
references "FormData"
alter table FormPropertyValues
add constraint FKB807B97165898A
foreign key (FieldId)
references "FormField"
یک مثال عملی:
امکانات فوق جهت طراحی قسمت ثبت اطلاعات یک برنامه «فرم ساز» مبتنی بر Key-Value بسیار مناسب هستند؛ برای مثال:
برنامهای را در نظر بگیرید که میتواند تعدادی خدمات داشته باشد که توسط مدیر برنامه قابل اضافه شدن است؛ برای نمونه خدمات درخواست نصب نرم افزار، خدمات درخواست تعویض کارت پرسنلی، خدمات درخواست مساعده، خدمات ... :
public class Service
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<ServiceFormField> Fields { get; set; }
public virtual IList<ServiceFormData> Forms { get; set; }
}
برای هر خدمات باید بتوان یک فرم طراحی کرد. هر فرم هم از یک سری فیلد خاص آن خدمات تشکیل شده است. برای مثال:
public class ServiceFormField
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual bool IsRequired { get; set; }
public virtual Service Service { get; set; }
}
در اینجا نیازی نیست به ازای هر فیلد جدید واقعا یک فیلد متناظر به دیتابیس اضافه شود و ساختار آن تغییر کند (برخلاف حالت dynamic components که پیشتر در مورد آن بحث شد).
اکنون با داشتن یک خدمات و فیلدهای پویای آن که توسط مدیربرنامه تعریف شدهاند، میتوان اطلاعات وارد کرد. مهمترین نکتهی آن هم IDictionary تعریف شده است که حاوی لیستی از فیلدها به همراه مقادیر وارد شده توسط کاربر خواهد بود:
public class ServiceFormData
{
public virtual int Id { get; set; }
public virtual IDictionary<ServiceFormField, string> FormPropertyValues { get; set; }
public virtual DateTime? DateTime { get; set; }
public virtual Service Service { get; set; }
}
در مورد نحوه نگاشت آن هم در حالت «ج» فوق توضیح داده شد.
عموما قلمهای فارسی، خصوصا مواردی که با B شروع میشوند مانند B Zar و امثال آن، فاقد تعاریف حروف مرتبط با glyphs الفبای انگلیسی است. نتیجه این خواهد شد که اگر متن شما مخلوطی از کلمات و حروف فارسی و انگلیسی باشد، فقط قسمت فارسی نمایش داده میشود و از قسمت انگلیسی صرفنظر خواهد شد. مرورگرها در این حالت هوشمندانه عمل میکنند و به یک قلم پیش فرض مانند Times و همانند آن جهت نمایش اینگونه متون مراجعه خواهند کرد؛ اما اینجا چنین اتفاقی نخواهد افتاد.
برای حل این مشکل، کلاسی به نام FontSelector در کتابخانهی iTextSharp وجود دارد. مثالی در این رابطه:
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
namespace HeadersAndFooters
{
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\\bzar.ttf");
Font bZar = FontFactory.GetFont("b zar", BaseFont.IDENTITY_H);
FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf");
Font tahoma = FontFactory.GetFont("tahoma", BaseFont.IDENTITY_H);
FontSelector fontSelector = new FontSelector();
//قلم اصلی
if (bZar.Familyname != "unknown")
{
fontSelector.AddFont(bZar);
}
//قلم پیش فرض در صورت نبود تعاریف مناسب در قلم اصلی
if (tahoma.Familyname != "unknown")
{
fontSelector.AddFont(tahoma);
}
var table1 = new PdfPTable(1);
table1.WidthPercentage = 100;
table1.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
var pdfCell = new PdfPCell { RunDirection = PdfWriter.RUN_DIRECTION_RTL, Border = 0 };
pdfCell.Phrase = fontSelector.Process("نمایش مخلوطی از متن فارسی و English با هم توسط قلمی که کاراکترهای انگلیسی را پشتیبانی نمیکند");
table1.AddCell(pdfCell);
pdfDoc.Add(table1);
}
//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
}
در این مثال از قلم B Zar استفاده شده است. اولین قلمی که به یک FontSelector اضافه میشود، قلم اصلی خواهد بود. قلم بعدی اضافه شده، قلم پیش فرض نام خواهد گرفت؛ به این معنا که در مثال فوق اگر قلم B Zar توانایی نمایش حرف جاری را داشت که خیلی هم خوب، در غیراینصورت به قلم بعدی مراجعه خواهد کرد و همینطور الی آخر. بنابراین این ترتیب اضافه کردن قلمها به FontSelector مهم است. نحوه استفاده نهایی از FontSelector تعریف شده هم در قسمت pdfCell.Phrase = fontSelector.Process مشخص است.
ابتدا میخواهیم یک الگو یا Template را درست کنیم و بعدها از روی آن، نامهی جدیدی
را ایجاد کنیم و فیلدهایش را پرکنیم. برای اینکار یک سند جدید را در Word
ایجاد و به سربرگ Mailings مراجعه میکنیم. سپس دکمهی Select Recipients
را بزنید. در ادامه از منوی باز شده، Type a NewList را بزنید. با اینکار پنجرهای باز
میشود. در اینجا دکمهی Customize Columns را بزنید. این پنجره شامل فیلدهایی میشود که
میتوانید از آن استفاده کنید و بر روی سند قرار دهید و داخل برنامه با پیدا کردن
این فیلدها میتوانید بجای آنها، مقدار مورد نظرتان را پاس دهید. حالا شما نیاز
دارید تا از طریق دکمهی Add، تمامی فیلدهای لازم یک نامه را بسازید. پس از این کار، در هر دو پنجره ،
دکمهی OK را بزنید. بدین صورت یک پنجرهی ذخیره برای شما باز میشود تا این فیلدهایی را
که ایجاد کردید، به عنوان یک دیتابیس کوچک ذخیره شود که تمامی فیلدها را
دارا میباشد و هر موقع که خواستید دوباره میتوانید از همین فیلدها استفاده
کنید.
حالا میرسیم به قرار دادن این فیلدها داخل سند. با ذخیره
کردن فیلدها، تمامی گزینههای سربرگ Mailings فعال میشود. شما برای اینکه
فیلدی را بر روی سند قرار دهید، روی Insert Merge Field کلیک و متناسب با نیازتان،
فیلدها را قرار دهید و الگو را طراحی کنید. یک نمونه:
حالا فایل را با پسوند DOT. ذخیره کنید. در ادامه این فایل را در دیتابیس، به این روش ذخیره کنید:
String FilePath = "Template Path" // Converting File to ByteArray byte[] FileBuffer = System.IO.File.ReadAllBytes(FilePath); // Now you can insert this file buffer to DB
الان، الگوی ما آمادهاست و میتوانیم از طریق برنامه، به این الگو دسترسی داشته باشیم و به آن پارامتر ارسال کنیم.
روش ارسال پارامترها به الگوهای Word
حالا
فرضا شما یک فرم دارید که از کاربر، اطلاعاتی را دریافت میکند و میخواهید همین
اطلاعات را به Word ارسال کنید. برای اینکار ابتدا باید
یک نمونه از الگویی را که طراحی کردهایم، داخل سیستم ذخیره کنیم. یعنی باید آنرا از
دیتابیس فراخوانی کنیم و آن آرایهی بایتی را، بر روی سیستم، تبدیل به فایل
کنیم. سپس از سمت برنامه، تمامی فیلدهای موجود در این الگو را خوانده و بجای تک تک آنها، مقدار مناسبی را قرار دهیم. در نهایت این فایل را توسط کدنویسی بر
روی سیستم کاربر ذخیره میکنیم. فایل را تبدیل به آرایه بایتی میکنیم، داخل
دیتابیس درج میکنیم و فایل را از سیستم کاربر حذف میکنیم.
بنابراین در ادامه ابتدا
Assembly مربوط به MicroSoft.Office.Interop.Word را به رفرنسهای پروژه اضافه
میکنیم و سربرگش را هم Using میکنیم.
حالا میرسیم به کد نویسی:
کدهای زیر را به صورت سراسری داخل فرم تعریف میکنیم:
//LOCATION OF THE TEMPLATE FILE ON THE MACHINE; Object oTemplatePath = string.Format("{0}\\NewDocument.dot", Application.StartupPath); //OBJECT OF MISSING "NULL VALUE" Object oMissing = System.Reflection.Missing.Value; //OBJECTS OF FALSE AND TRUE Object oTrue = true; Object oFalse = false; //CREATING OBJECTS OF WORD AND DOCUMENT Microsoft.Office.Interop.Word.Application oWord = null; Microsoft.Office.Interop.Word.Document oWordDoc = null;
// Fetching Template ByteArray From Database => Byte[] YourTemplateByteArray = Fetch Template; System.IO.File.WriteAllBytes(oTemplatePath.ToString(), YourByteArray); oWord = new Microsoft.Office.Interop.Word.Application(); oWordDoc = new Microsoft.Office.Interop.Word.Document(); //Adding A New Document From A Template oWordDoc = oWord.Documents.Add(ref oTemplatePath, ref oMissing, ref oMissing, ref oMissing); int iTotalFields = 0; // Finding Mailmerge Fields foreach(Microsoft.Office.Interop.Word.Field myMergeField in oWordDoc.Fields) { iTotalFields++; Microsoft.Office.Interop.Word.Range rngFieldCode = myMergeField.Code; String fieldText = rngFieldCode.Text; // Only Get The Mailmerge Fields if (fieldText.StartsWith(" MERGEFIELD")) { // Gives The Fieldnames as Entered in .DOT File string fieldName = fieldText.Substring(12, fieldText.IndexOf(" ", 12) - 12); switch (fieldName) { case "Letter_No": myMergeField.Select(); oWord.Selection.TypeText(txtLetterNo.Text); break; case "Letter_Date": myMergeField.Select(); oWord.Selection.TypeText(DateTime.Now); break; case "Letter_Has_Attachment": myMergeField.Select(); oWord.Selection.TypeText("دارد یا ندارد"); break; // And So On default: break; } } } //Showing The Document To The User oWord.Visible = true;
در ادامه یک دکمه را برای ذخیرهی فایل ورد قرار میدهیم. زمانیکه کاربر تایپ کردنش تمام شد و هنوز برنامهی ورد در حال اجراست، این دکمه را اجرا میکند. دقت کنید برنامهی ورد نباید بسته شود؛ باید باز باشد. بعد دکمهی ذخیره را میزنیم. با کدنویسی، برنامهی Word را خودمان میبندیم؛ نیازی به دخالت کاربر نیست.
oWordDoc.Save(); //Closing the file oWordDoc.Close(ref oFalse, ref oMissing, ref oMissing); //Quitting the application oWord.Quit(ref oMissing, ref oMissing, ref oMissing); byte[] FileBuffer = System.IO.File.ReadAllBytes(oTemplatePath.ToString ()); // Now Insert The FileBuffer Into Database as A Letter
خوب؛ کار تمام است! حالا فیلد FileBuffer را باید بسته به کدنویسی خودتان، داخل دیتابیس ذخیره کنید که برای بعدها بتوانید آنرا واکشی کرده و به کاربر نمایش دهید. این هم نمونهی نهایی جایگذاری فیلدها:
این آموزش را خیلی سال پیش در این تاپیک داخل فوروم برنامه نویس نوشته بودم.
آموزش 0 تا صد بوت استرپ
آموزش بوت استرپ از 0 تا صد همگی در tutorialrepublic
در کتابخانهی iTextSharp به جهت سازگاری با کتابخانهی اصلی، رنگها را بر اساس کلاسی به نام BaseColor تعریف کردهاند؛ که ایکاش به جای اینکار، همه را با کلاس Color فضای نام استاندارد System.Drawing جایگزین میکردند. همین مشکل با فونت هم هست. یک کلاس فونت در فضای نام iTextSharp.text وجود دارد به علاوه کلاس فونت تعریف شده در فضای نام استاندارد System.Drawing دات نت؛ که خیلی سریع میتواند به خطای کامپایل زیر ختم شود:
'Font' is an ambiguous reference between 'iTextSharp.text.Font' and 'System.Drawing.Font'
و در نهایت مجبور خواهیم شد که به صورت صریح علام کنیم، iTextSharp.text.Font منظور ما است و نه آن یکی.
در کل اگر با کلاس Color فضای نام استاندارد System.Drawing بیشتر راحت هستید به صورت زیر هم میتوان رنگهای متداول را مورد استفاده قرار داد:
تعریف رنگها بر اساس نام آنها:
var color = new BaseColor(Color.LightGray);
تعریف رنگها بر اساس مقادیر Hex متداول در المانهای HTML :
var color = new BaseColor(ColorTranslator.FromHtml("#1C5E55"));
این نکتهای است که شاید خیلیها از وجود آن بیاطلاع باشند. به صورت پیش فرض در کلاس استاندارد ColorTranslator، امکان دریافت رنگهای بکاررفته در المانهای HTML به کمک متد ColorTranslator.FromHtml مهیا است.
البته اگر زمانی خواستید خودتان این متد را پیاده سازی کنید، نکتهی آن به صورت زیر است:
string htmlColor = "#1C5E55";
int x = Convert.ToInt32(htmlColor.Replace("#", "0x"), 16);
byte red = (byte)((x & 0xff0000) >> 16);
byte green = (byte)((x & 0xff00) >> 8);
byte blue = (byte)(x & 0xff);
سپس کلاسهای Color و همچنین BaseColor امکان پذیرش این اجزای حاصل را دارند (به کمک متد Color.FromRgb و یا سازندهی BaseColor).
علت ذکر ColorTranslator.FromHtml به این بر میگردد که ترکیبات رنگ جالبی را میتوان از جداول HTML ایی موجود در سایتهای مختلف ایده گرفت و یا حتی از قالبهای پیش فرض GridView در ASP.NET مثلا.
اکنون برای ساخت جدولی مانند شکل فوق، به ازای هر سلولی که مشاهده میکنید باید یکبار BorderColor و BackgroundColor تنظیم شوند. رنگ متن هم از رنگ فونت دریافت میشود:
var pdfCell = new PdfPCell(new Phrase(Text, Font))
{
RunDirection = ...,
BorderColor = ...,
BackgroundColor = ...
};
var row = new string[3]; row[0] = "Liverpool"; row[0] = "15";
public class Performance { public string TeamName { get; set; } public int Score { get; set; } }
در نگارش 2x هم در متد GenerateUserIdentityAsync (که به همراه اطلاعات ApplicationUser وارد شدهی به سیستم هست) فرصت خواهید داشت تا یک Claim جدید را اضافه کنید (همانجایی که نوشته شده Add custom user claims here):
// Add custom user claims here userIdentity.AddClaim(new Claim("key1", "value1"));
public static string GetClaimValue(this IPrincipal currentPrincipal, string key) { var identity = currentPrincipal.Identity as ClaimsIdentity; if (identity == null) return null; var claim = identity.Claims.FirstOrDefault(c => c.Type == key); return claim?.Value; }