private static void LoadEntities(Assembly asm, DbModelBuilder modelBuilder, string nameSpace) { var entityTypes = asm.GetTypes() .Where(type => type.BaseType != null && type.Namespace == nameSpace && type.BaseType == null) .ToList(); entityTypes.ForEach(modelBuilder.RegisterEntityType); }
خوب، فرض کنید میخواهید اکشن متدی رو بنویسید که قراره نام یک فایل متنی رو بگیره و انو تو مروگر به کاربر نمایش بده.
برای اینکار از کلاس ActionResult، کلاس دیگهی رو بنام TextResult به ارث میبریم و از این ActionResult سفارشی شده، در اکشن متد مربوطه استفاده میکنیم:
public class TextResult : ActionResult { public string FileName { get; set; } public override void ExecuteResult(ControllerContext context) { var filePath = Path.Combine(context.HttpContext.Server.MapPath(@"~/Files/"), FileName); var data = File.ReadAllText(filePath); context.HttpContext.Response.Write(data); } }
public ActionResult DownloadTextFile(string fileName) { return new TextResult { FileName = fileName }; }
خوب، سوالی که اینجا پیش میاد اینه که چرا این همه کار اضافی، چرا از Return File استفاده نمیکنی؟
public ActionResult DownloadTextFile(string fileName) { var filePath = Path.Combine(HttpContext.Server.MapPath(@"~/Files/"), fileName); return File(filePath, "text"); }
- جلوگیری از پیچیدگی و تکرار کد
به مثال زیر توجه کنید که قراره خروجی CSV بهمون بده.
public class CsvActionResult : ActionResult { public IEnumerable ModelListing { get; set; } public CsvActionResult(IEnumerable modelListing) { ModelListing = modelListing; } public override void ExecuteResult(ControllerContext context) { byte[] data = new CsvFileCreator().AsBytes(ModelListing); var fileResult = new FileContentResult(data, "text/csv") { FileDownloadName = "CsvFile.csv" }; fileResult.ExecuteResult(context); } }
public ActionResult ExportUsers() { IEnumerable<User> model = UserRepository.GetUsers(); return new CsvActionResult(model); }
- راحت کردن گرفتن تست واحد از اکشنها متدها
به مثال زیر توجه کنید که قراره برای اکشن Logout تست واحد بنویسیم
ابتدا بردن وابستگیها به خارج از اکشن به کمک ActionResult سفارشی
public class LogoutActionResult : ActionResult { public RedirectToRouteResult ActionAfterLogout { get; set; } public LogoutActionResult(RedirectToRouteResult actionAfterLogout) { ActionAfterLogout = actionAfterLogout; } public override void ExecuteResult(ControllerContext context) { FormsAuthentication.SignOut(); ActionAfterLogout.ExecuteResult(context); } }
public ActionResult Logout() { var redirect = RedirectToAction("Index", "Home"); return new LogoutActionResult(redirect); }
[TestMethod] public void The_Logout_Action_Returns_LogoutActionResult() { //arrange var account = new AccountController(); //act var result = account.Logout() as LogoutActionResult; //assert Assert.AreEqual(result.ActionAfterLogout.RouteValues["Controller"], "Home"); }
منابع و مراجع: + و +
روی متدهای GetAllCategory و GetAllNews به صورت جداگانه کلیک کنید. متوجه خواهید شد که هرچند در کلاس tblNews شیای از نوع tblCategory و در کلاس tblCategory شیای از نوع مجموعهی tblNews به صورت Virtual تعریف شده است ولی در بر خلاف انتظارمان اثری از آن در اینجا دیده نمیشود. نتیجهی مشاهدهشده به خاطر است که در هر دو تعریف صفت DataMember را به ویژگیهای ناوبری اختصاص نداده ایم و این میتواند راهبرد ما در طراحی WCF باشد. ولی اگر میخواهید ویژگی ناوبری میان موجودیتها در متدهای ما هم دیده شود ادامهی این درس را بخوانید وگرنه ممکن است تصمیم داشته باشید در صورت نیاز به پیوند میان موجودیتها، متد جدیدی بنویسید و از دستورهای Linq استفاده کنید و یا برای اینکار از Stored Procedured بهره ببرید.
در اینجا من این سناریو را دنبال میکنم که در صورتی که متد GetAllNews اجرا شود؛ بدون اینکه نیاز باشد برای دانستن نام دستهی خبر از متد دیگری مانند GetAllCategory استفاده کنیم؛ رکورد وابسته موجودیت دسته در هر خبر نشان داده شود.
از Solution Explorer فایل MyNewsModel.tt را باز کنید و دنبال کد زیر بگردید:
public string NavigationProperty(NavigationProperty navigationProperty) { var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType()); return string.Format( CultureInfo.InvariantCulture, "{0} {1} {2} {{ {3}get; {4}set; }}", AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)), navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType, _code.Escape(navigationProperty), _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)), _code.SpaceAfter(Accessibility.ForSetter(navigationProperty))); }
سپس آنرا به صورت زیر ویرایش کنید:
public string NavigationProperty(NavigationProperty navigationProperty) { var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType()); return string.Format( CultureInfo.InvariantCulture, "{0}{1} {2} {3} {{ {4}get; {5}set; }}", navigationProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many ? "[DataMember]" + Environment.NewLine : "", AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)), navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType, _code.Escape(navigationProperty), _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)), _code.SpaceAfter(Accessibility.ForSetter(navigationProperty))); }
پس از ذخیرهی فایل، خواهید دید که صفت DataMember در کلاس tblNews پیش از ویژگی tblCategory افزوده شده است. بار دیگر پروژه را اجرا کنید. روی متد GetAllNews کلیک کنید و روی دکمه Invoke بفشارید. خواهید دید که هرچند tblCategory در ویژگیهای آن قرار گرفته است ولی مقدار آن Null است. برای حل این مشکل باید از Solution Explorer فایل MyNewsService.cs را باز کنید و به به جای کد مربوط به متدهای GetAllNews و GetNews کدهای زیر را قرار دهید:
public List<tblNews> GetAllNews()
{
return dbMyNews.tblNews.Include(p=>p.tblCategory).Where(c=>c.IsDeleted == false).ToList();
}
public tblNews GetNews(int tblNewsId)
{
return dbMyNews.tblNews.Include(p => p.tblCategory).FirstOrDefault(p => p.tblNewsId == tblNewsId);
}
این بار اگر پروژه را اجرا کنید با نتیجهای مانند شکل زیر روبهرو خواهید شد:
در بخش هفتم پیرامون میزبانی WCF Library خواهم نوشت.
private async Task<(Token Token, Dictionary<string, string> AppCookies)> LoginAsync(string requestUri, string username, string password) { var viewmodel = new LoginViewModel { Username = username, Password = password }; var host = new Uri("http://localhost:5000"); var httpClient = _httpClientFactoryService.GetOrCreate(host); var responseMessage = await httpClient.PostAsJsonAsync("api/account/login", viewmodel).ConfigureAwait(false); var responseContent = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); // return Content(responseContent); ... }
یازده. در جاوا رویدادها با استفاده از اینترفیسها پیاده سازی میشوند. برای نامگذاری یک رویداد، قاعده آن در جاوا بدین شکل است که نامها به صورت (+ ) Camel نوشته شده و آخرین عبارت هم Listener باشد و نیازی هم به حرف I در نامگذاری اینترفیس نیست؛ چون همه میدانند که این Listener آخری یعنی رویدادی که با اینترفیس پیاده سازی شده است و استفاده از I بی معنی است. هر چند بر خلاف دات نت، در اینجا استفاده از قاعده I چندان متداول نیست.
public interface CopyFileListener { void PublishProgress(long fileSize,long copiedSize); }
دوازده. گوگل اینترفیسهایی را که برای رویدادها میسازد، داخل کلاس اصلی تعریف میکند. پس بهتر هست که شما هم همین روند را ادامه بدید و از این قاعده خارج نشوید. اگر خوب دقت کرده باشید، در برنامه نویسی اندروید تمام اینترفیسها داخل کلاس اصلی هستند:
textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } });
public class MemoryWare { public interface CopyFileListener { void PublishProgress(long fileSize,long copiedSize); } .... }
SetOnClickListener
editText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { } });
سیزده. آداپتورها و آداپتور ویوها (چون لیست) قسمت مهمی از برنامههای اندرویدی به شمار میآیند؛ تا حدی که در بیشتر برنامههای ساده هم حضور پررنگی دارند. ولی برای استفاده از این آداپتورها باید بدانید که نحوه کار آنها چگونه است. بسیاری از کاربران در این قسمت اشتباهات زیادی میکنند. اگر در stackOverflow هم در اینباره نگاه کنید، با حجم انبوهی از سوالات روبرو میشوید و فقط به خاطر اینکه نحوه کارکرد آن را نمیدانند، به مشکل برخوردهاند.
کلاس BaseAdapter اصلیترین کلاس آداپتور هاست که بقیه از آن مشتق شدهاند و معروفترین مشتقات آن، کلاسهای CursorAdapter و ArrayAdapter هستند که امکانات بیس آداپتور را افزایش دادهاند.به عنوان مثال در کد پایین از ArrayAdapter استفاده شده است.
نحوه کار یک آداپتور بدین صورت است که متدی را به نام GetView با قابلیت override دارد که با هر تعداد آیتم موجود صدا زده میشود. ولی اگر تصور کنیم فقط چند صدهزار آیتم هم داشته باشیم، آیا واقعا اجرا میشود؟ جواب این سوال این است که با هر بار اسکرولی که شما میکنید آیتمهای بعدی ایجاد میشوند ولی باز این سوال پیش میآید که هر آیتم برای خود جداگانه تشکیل میشود؟ مطمئنا جواب خیر است. آداپتورها از سیستمی به نام ViewRecycler برای کش کردن آیتمهای ایجاد شده استفاده میکنند و با هر اسکرولی که انجام میشود آیتمهای بعدی از روی آیتمهای قبلی که قبلا از صفحه خارج شدهاند، ساخته میشوند و آیتمهای کش شده قبلی را با پارامتری با نام convertView به دست شما میرساند.
کد زیر را ببینید:
@Override public View getView(int position, View rowView, ViewGroup parent) { ViewHolder viewHolder=null; if(rowView==null) { // 1. Create inflater LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); // 2. Get rowView from inflater rowView = inflater.inflate(R.layout.row_bank_group_list, parent, false); viewHolder=new ViewHolder(); viewHolder.txtGroupName=(TextView) rowView.findViewById(R.id.text_groupName); rowView.setTag(viewHolder); } else { viewHolder=(ViewHolder)rowView.getTag(); } viewHolder.txtGroupName.setText(getItem(position).getName()); viewHolder.txtGroupName.setTypeface(new FontSystem().get_General_Font(context)); viewHolder.txtGroupName.setTextColor(context.getResources().getColor(R.color.black)); return rowView; }
کلاس داخلی ViewHolder هم یک الگو برای عدم بررسی Viewهای داخل آن است که نیازی به یافتن و تبدیل مجدد آنها نداشته باشید. در این روش شیء، داخل خصوصیت tag آیتم قرار گرفته است و وقتی از کش برداشته شود، خاصیت تگ آن را میخوانیم و مستقیما مورد استفاده قرار میدهیم. در این حالت شما بهترین استفاده را از پردازشها و حافظه، میکنید.
چهارده. یکی از کارهایی را که قبل از کار کردن در یک مسیر فیزیکی باید انجام دهید این است که مطمئن باشید اجازه نوشتن در آن ناحیه را دارید یا خیر. در غیر اینصورت برنامه شما با خطای FC روبرو میشود و اجرای آن خاتمه مییابد. به همین دلیل اکثر برنامه نویسان از متد CanWrite در کلاس File استفاده میکنند. ولی در هنگام استفاده از این متد باید دقت داشته باشید که کلاس File فقط باید حاوی مسیر باشد و اسمی از فایل مربوطه در آن نباشد. دلیل هم آن است که این احتمال میرود اگر فایلی هم وجود نداشته باشد، مقدار false را به شما برگرداند. مثال زیر قرار است فایلی را در کارت حافظه بنویسید، ولی بررسی اجازه نوشتن در مسیر، اشتباه است:
File file=new File(sdcardPath,fileName); if(file.CanWrite()) { ..... }
File file=new File(sdcardPath); if(file.CanWrite()) { file=new File(sdcardPath,filePath); ..... }
پانزده. کارت حافظه خارجی: همه برنامه نویسان اندروید حداقل یکبار از کد زیر استفاده کرده اند:
Environment.getExternalStorageDirectory()
هر برنامهای که در اندروید نصب میشود در مسیر
/Data/Data
/Data/Data/Info.Dotnettips.MyApp
/** * it will returns sd path for you * <p> * <b>Required Permission: </b>android.permission.READ_EXTERNAL_STORAGE<br/> * </p> * @return */ public List<String> GetExternalMounts() { final List<String> out = new ArrayList<>(); String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*"; String s = ""; try { final Process process = new ProcessBuilder().command("mount") .redirectErrorStream(true).start(); process.waitFor(); final InputStream is = process.getInputStream(); final byte[] buffer = new byte[1024]; while (is.read(buffer) != -1) { s = s + new String(buffer); } is.close(); } catch (final Exception e) { e.printStackTrace(); } // parse output final String[] lines = s.split("\n"); for (String line : lines) { if (!line.toLowerCase(Locale.US).contains("asec")) { if (line.matches(reg)) { String[] parts = line.split(" "); for (String part : parts) { if (part.startsWith("/")) if (!part.toLowerCase(Locale.US).contains("vold")) if(new File(part).canWrite()) out.add(part); } } } } return out; }
شانزده. یکی از روشهای انتقال اطلاعات بین اکتیویتیها مختلف استفاده از Extras هاست که شما با تعیین یک نام یا کلید، اطلاعات مربوطه را ارسال و توسط همان کلید؛ اطلاعات را در اکتیویتی مقصد دریافت میکنید:
notesIntent.putExtra("PartyId", PartyId); startActivity(notesIntent);
PartyId=getIntent().getLongExtra("PartyId",0);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false); i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false); i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_FILE);
هفده. قواعد نامگذاری: برای نامگذاری متغیرها از قانون CamelCase استفاده میکنیم. ولی برای حالات زیر از روشهای دیگر استفاده میشود:
- برای ثابتها از حروف بزرگ و _ استفاده کنید.
- برای متغیرهای خصوصی از حرف m در ابتدای نام متغیر استفاده کنید.
- برای متغیرهای استاتیک از حرف s در ابتدای نام متغیر استفاده کنید.
public class MyClass { public static final int SOME_CONSTANT = 42; public int publicField; private static MyClass sSingleton; int mPackagePrivate; private int mPrivate; protected int mProtected; }
هجده: قاعده نظم و ترتیب در importها توسط مستندات گوگل بدین شکل تعریف شده است:
- نام پکیجهای ارائه شده توسط گوگل
- نام پکیجهای ثالث
- نام پکیجهای موجود در java و javax
- پکیجهای موجود در پکیج اصلی
نوزدهم. مرتب سازی متدهای دسترسی یک کلاس: بسیار خوب است که همیشه کدهای ما نظم خاصی را داشته باشد تا پیگیریهای شخصی و تیمی در آن راحتتر صورت بگیرد. برای مثال در یک کلاس ابتدا متدهای public و سپس private قرار گیرند و الی آخر.
الگوی عمومی که برای کار با جاوا صورت گرفته است به شکل زیر میباشد:
public, protected, private,abstract, static, transient, volatile, synchronized, final, native.
ادیتور intelij شامل تنظمیاتی برای مرتب سازی کدهاست که در این مورد بسیار سودمند است. با طی کردن مسیر زیر میتوانید برای آن ترتیب اینگونه موارد را مشخص کنید.
Settings>Editor>Code Style>Arrangement
در تصویر بالا متدها به ترتیب متدهای دستری بین بلوکهای کامنت method start و method end قرار گرفته اند.
همچنین شامل گزینههای دیگری نیز میباشد که به نظرم فعال کردنشان بسیار خوب است. گزینه keep overridden methods together به شما کمک میکند تا متدهایی را که رونویسی میشوند، در کنار یکدیگر قرار بگیرند که برای کلاسهای اندرویدی مثل اکتیویتیها و فرگمنتها و ... بسیار خوب است. گزینه مفید دیگر Keep dependent methods together است که در دو حالت عمقی یا خطی متدهای وابسته (متدهایی که متدهای دیگر را در آن کلاس صدا میزنند) در کنار یکدیگر قرار میدهد و مابقی گزینهها، که بسیار سودمند هست. به هر حال هر قاعدهای را که برای خود انتخاب میکنید اگر در حالت پیش فرض نیست بهتر است در مستندات پروژه ذکر شود تا افراد دیگر سریعتر به موضوع پی ببرند.
قسمت بیستم. این مورد برای افراد تازه کار میباشد که تازه اندروید استادیو را باز کردهاند و مشغول کدنویسی میباشند. یکی از مواردی که در همان مرحله اول به آن برمیخورید این است که intellisense ادیتور به بزرگی و کوچکی حروف حساس است و تنها با حرف اول سازگاری دارد. برای تغییر این مسئله باید مسیر زیر را طی کنید:
Settings>Editor>Completion>Case-sensitive Completion>None
public static Font GetTahoma() { var fontName = "Tahoma"; if (!FontFactory.IsRegistered(fontName)) { var fontPath = Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf"; FontFactory.Register(fontPath); } return FontFactory.GetFont(fontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED); }
string fileNameExisting = @"Test.pdf"; string fileNameNew = @"newform.pdf"; using (var existingFileStream = new FileStream(fileNameExisting, FileMode.Open)) using (var newFileStream = new FileStream(fileNameNew, FileMode.Create)) { var pdfReader = new PdfReader(existingFileStream); using (var stamper = new PdfStamper(pdfReader, newFileStream)) { //نکته مهم جهت کار با اطلاعات فارسی //در غیراینصورت شاهد ثبت اطلاعات نخواهید بود stamper.AcroFields.AddSubstitutionFont(GetTahoma().BaseFont); //form.Fields.Keys = تمام فیلدهای موجود در فرم var form = stamper.AcroFields; //مقدار دهی فیلدهای فرم form.SetField("Text1", "مقدار1"); form.SetField("Text2", "مقدار2"); form.SetField("Text3", "مقدار3"); form.SetField("Text4", "مقدار4"); form.SetField("Text5", "مقدار5"); form.SetField("Text6", "مقدار6"); // به این ترتیب فرم دیگر توسط کاربر قابل ویرایش نخواهد بود //stamper.PartialFormFlattening --> جهت غیرقابل ویرایش نمودن فیلدی مشخص stamper.FormFlattening = true; stamper.Close(); pdfReader.Close(); } } Process.Start("newform.pdf");
و محتوای فایل جدید که برای من ایجاد میکند
ممنون .
عموما بر روی سرورهای برنامههای وب، نرم افزار خاصی نصب نمیشود. برای مثال اگر نیاز به تولید فایل اکسل بر روی سرور باشد، سرور دار بعید است که آفیس را برای شما نصب کند و همچنین مایکروسافت هم این یک مورد را اصلا توصیه و پشتیبانی نمیکند (ایجاد چندین وهله از برنامه آفیس (تعامل با اشیاء COM) بر روی سرور توسط یک برنامهی وب چند کاربره).
اگر سایتها را هم جستجو کنید پر است از مقالاتی مانند تبدیل GridView به اکسل ... که تنها هنر آنها انتخاب قسمت table مانند GridView و رندر کردن آن در مرورگر با پسوندی به نام xls یا xlsx است. به عبارتی فایل نهایی تولید شده استاندارد نیست. فقط یک html table است با پسوند xls/xlsx که برنامهی اکسل میداند به چه صورتی باید آنرا باز کند (که گاها در این بین فارسی سازی آن مشکل ساز میشود). این فایل نهایی تولیدی عاری است از امکانات پیشرفته و حرفهای اکسل. برای مثال اضافه کردن فرمول به آن، تبدیل اطلاعات به نمودارهای اکسل به صورت خودکار، داشتن فایلی با چندین work sheet مختلف، اعمال قالبهای مختلف، صفحه بندی بهتر و غیره.
مایکروسافت از سال 2007 تولید فایلهای آفیس را با معرفی استاندارد OpenXML که توسط مؤسسه ایزو هم پذیرفته شده، بسیار سادهتر کرده است. OpenXML SDK در دسترس است و توسط آن میتوان فایلهای اکسل حرفهای را بدون نیاز به نصب مجموعهی آفیس تولید کرد. کار کردن با OpenXML SDK هم در نگاه اول شاید ساده به نظر برسد اما آن هم ریزه کاریهای خاص خودش را دارد که نمونهای از آنرا در مطلب "تولید فایل Word بدون نصب MS Word بر روی سرور" میتوانید مشاهده کنید. به عبارتی این مجموعه جهت نوشتن کتابخانههای ویژهی شما باز است ...
در این بین یکی از حرفهایترین کتابخانههایی که امکانات تولید فایلهای اکسل را به کمک OpenXML SDK سهولت میبخشد، کتابخانهی سورس باز EPPlus است:
مثالی در مورد نحوهی استفاده از آن:
میخواهیم یک DataTable را به یک فایل اکسل واقعی (نه یک html table با پسوند xlsx) تبدیل کنیم با این شرایط که یکی از قالبهای جدید آفیس به آن اعمال شود؛ جمع کل یکی از ستونها توسط اکسل محاسبه گردیده و همچنین عرض دقیق ستونها نیز در برنامه تنظیم گردد. نموداری نیز به صورت خودکار این اطلاعات را نمایش دهد:
using System.Data;
using System.IO;
using OfficeOpenXml;
using OfficeOpenXml.Drawing.Chart;
using OfficeOpenXml.Style;
using OfficeOpenXml.Table;
namespace EPPlusTest
{
class Program
{
static void Main(string[] args)
{
var newFile = new FileInfo("Test.xlsx");
if (newFile.Exists)
{
newFile.Delete();
}
//ایجاد یک سری اطلاعات دلخواه
var table = createDt();
using (var package = new ExcelPackage(newFile))
{
// اضافه کردن یک ورک شیت جدید
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("مخارج");
//اضافه کردن یک جدول جدید از دیتاتیبل دریافتی
worksheet.Cells["A1"].LoadFromDataTable(table, true, TableStyles.Dark9);
//نمایش جمع ستون هزینههای ماهها
var tbl = worksheet.Tables[0];
//زیر آخرین ردیف یک سطر اضافه میکند
tbl.ShowTotal = true;
//فرمول نحوهی محاسبه جمع ستون انتساب داده میشود
tbl.Columns[1].TotalsRowFunction = RowFunctions.Sum;
//تعیین عرض ستونهای جدول
worksheet.Column(1).Width = 14;
worksheet.Column(2).Width = 12;
//تنظیم متن هدر
worksheet.HeaderFooter.oddHeader.CenteredText = "مثالی از نحوهی استفاده از ایی پی پلاس";
//میخواهیم سرستونها در وسط ستون قرار گیرند
worksheet.Cells["A1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
worksheet.Cells["B1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
//افزودن یک نمودار جدید به شیت جاری
var chart = worksheet.Drawings.AddChart("chart1", eChartType.Pie3D);
chart.Title.Text = "نمودار هزینههای سال";
chart.SetPosition(Row: 2, RowOffsetPixels: 5, Column: 3, ColumnOffsetPixels: 5);
chart.SetSize(PixelWidth: 320, PixelHeight: 360);
chart.Series.Add("B2:B13", "A2:A13");
chart.Style = eChartStyle.Style26;
//تنظیم یک سری خواص فایل نهایی
package.Workbook.Properties.Title = "مثالی از ایی پی پلاس";
package.Workbook.Properties.Author = "وحید";
package.Workbook.Properties.Subject = "ایجاد فایل اکسل بدون نرم افزار اکسل";
//تنظیم نحوهی نمایش فایل زمانیکه در نرم افزار اکسل گشوده میشود
worksheet.View.PageLayoutView = true;
worksheet.View.RightToLeft = true;
// ذخیر سازی کلیه موارد اعمالی در فایل
package.Save();
}
}
private static DataTable createDt()
{
var table = new DataTable("مخارج");
table.Columns.Add("ماه", typeof(string));
table.Columns.Add("هزینه", typeof(decimal));
table.Rows.Add("فروردین", 100);
table.Rows.Add("اردیبهشت", 250);
table.Rows.Add("خرداد", 80);
table.Rows.Add("تیر", 300);
table.Rows.Add("مرداد", 200);
table.Rows.Add("شهریور", 150);
table.Rows.Add("مهر", 250);
table.Rows.Add("آبان", 200);
table.Rows.Add("آذر", 400);
table.Rows.Add("دی", 100);
table.Rows.Add("بهمن", 130);
table.Rows.Add("اسفند", 80);
return table;
}
}
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class SanitizeInputAttribute : ActionFilterAttribute { var sanitizer = new Ganss.XSS.HtmlSanitizer(); public override void OnActionExecuting(ActionExecutingContext filterContext) { if (filterContext.ActionArguments != null) { foreach (var parameter in filterContext.ActionArguments) { var properties = parameter.Value.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public) .Where(x => x.CanRead && x.CanWrite && x.PropertyType == typeof(string) && x.GetGetMethod(true).IsPublic && x.GetSetMethod(true).IsPublic); foreach (var propertyInfo in properties) { if (propertyInfo.GetValue(parameter.Value) != null) propertyInfo.SetValue(parameter.Value, sanitizer.Sanitize(propertyInfo.GetValue(parameter.Value).ToString())); } } } } }
[SanitizeInput] public IActionResult Add(GroupDto dto)
محدودیتهای کار با اشیاء COM در NET Core 2x.
پیاده سازی پشتیبانی از اشیاء COM در NET Core 2x. به همراه اینترفیس IDispatch نیست. به این معنا که از مفهوم «late binding» پشتیبانی نمیکند. حدود 10 سال قبل در زمان ارائهی C# 4.0، واژهی کلیدی dynamic نیز ارائه شد که یکی از مهمترین اهداف آن، ساده سازی کار با اشیاء COM و پشتیبانی از Late binding بود:
dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application", true)); excel.Visible = true; Console.WriteLine("Press Enter to close Excel."); Console.ReadLine(); excel.Quit();
System.__ComObject does not contain a definition for 'Visible'
یک نکته: NET Core 3x. از Late binding پشتیبانی میکند.
روش کار با اشیاء COM در NET Core 2x.
چون NET Core 2x. از late binding اشیاء COM پشتیبانی نمیکند، میتوان در اینجا از روش قدیمیتر کار با اشیاء COM که استفادهی از «Interop assemblies» نام دارد، استفاده کرد. Interop assemblies در حقیقت محصور کنندههای اشیاء COM هستند که امکان کار مستقیم با آنها را از طریق early binding میسر میکنند. در یک چنین حالتی، کدهای فوق برای دسترسی به اشیاء COM کار با اکسل، به صورت زیر که early binding نام دارد، تغییر میکند:
using Excel = Microsoft.Office.Interop.Excel; // ... var excel = new Excel.Application(); excel.Visible = true; Console.WriteLine("Press Enter to close Excel."); Console.ReadLine(); excel.Quit();
روش تولید Interop assemblies
هنوز خود NET Core. روشی را برای تولید Interop assemblies ارائه ندادهاست و تولید آنها یکی از معدود مواردی است که نیاز به نصب Visual Studio را دارد. برای این منظور یک پروژهی خالی (از هر نوعی) را که بر اساس NET Framework 4x. تهیه میشود، در VS آغاز کنید و سپس در solution explorer بر روی پروژهی ایجاد شده کلیک راست کرده و گزینهی Add > Reference را انتخاب کنید. در صفحهی باز شده، گزینهی COM آنرا باید انتخاب کنید. در اینجا است که میتوانید با انتخاب یکی از موارد، ارجاعی را به آن شیء COM اضافه کنید.
پس از اینکار:
- ابتدا این ارجاع اضافه شده را در solution explorer انتخاب کرده و در پایین صفحه، در قسمت برگهی خواص آن، گزینهی «Embed Interop Types» آنرا به false تنظیم کنید.
- سپس یکبار پروژه را نیز کامپایل کنید.
این مراحل سبب تولید یک فایل dll خواهند شد که Interop assembly نام دارد و هم در برنامههای NET. و هم NET Core.، قابل استفادهاست.
روش استفاده از Interop assemblies در برنامههای NET Core.
اکنون که یک فایل dll را از شیء COM انتخابی، در یک پروژهی مجزای مبتنی بر NET 4x. تولید کردیم، روش استفادهی از آن در یک برنامهی دیگر مبتنی بر NET Core. به صورت زیر است:
<ItemGroup> <Reference Include="Interop.WIA"> <HintPath>..\DNTScanner.Core.TypeLibrary\bin\Debug\Interop.WIA.dll</HintPath> <EmbedInteropTypes>True</EmbedInteropTypes> </Reference> </ItemGroup>
یک نکته: اگر EmbedInteropTypes را به true تنظیم کردید، نیاز به بستهی Microsoft.CSharp را نیز خواهید داشت:
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' "> <Reference Include="Microsoft.CSharp" /> </ItemGroup> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <PackageReference Include="Microsoft.CSharp" Version="4.5.0" /> </ItemGroup>
روش دیگر استفاده از Interop assemblies در برنامههای NET Core.
روش فوق، جهت کار با فایلهای dll ای است که خودمان تولید کردهایم. برای سایر حالاتی که این موارد در سیستم نصب شدهاند (مانند Office Primary Interop Assemblies (PIA))، پس از افزودن ارجاعی به COM reference مدنظر، فایل csproj همان پروژهی NET 4x. را باز کرده و قسمت COMReference آنرا در اینجا (در فایل csproj پروژهی NET Core.) کپی کنید:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.0</TargetFramework> </PropertyGroup> <!-- The following 'COMReference' items were copied from a .NET Framework project. They were added by using the Visual Studio COM References window. See https://docs.microsoft.com/en-us/visualstudio/ide/managing-references-in-a-project?view=vs-2017. Observe the 'EmbedInteropTypes' tag value. See https://docs.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-items?view=vs-2017#comreference --> <ItemGroup> <COMReference Include="Microsoft.Office.Core"> <Guid>{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}</Guid> <VersionMajor>2</VersionMajor> <VersionMinor>8</VersionMinor> <Lcid>0</Lcid> <WrapperTool>primary</WrapperTool> <Isolated>False</Isolated> <EmbedInteropTypes>True</EmbedInteropTypes> </COMReference> <COMReference Include="Microsoft.Office.Interop.Excel"> <Guid>{00020813-0000-0000-C000-000000000046}</Guid> <VersionMajor>1</VersionMajor> <VersionMinor>9</VersionMinor> <Lcid>0</Lcid> <WrapperTool>primary</WrapperTool> <Isolated>False</Isolated> <EmbedInteropTypes>True</EmbedInteropTypes> </COMReference> <COMReference Include="VBIDE"> <Guid>{0002E157-0000-0000-C000-000000000046}</Guid> <VersionMajor>5</VersionMajor> <VersionMinor>3</VersionMinor> <Lcid>0</Lcid> <WrapperTool>primary</WrapperTool> <Isolated>False</Isolated> <EmbedInteropTypes>True</EmbedInteropTypes> </COMReference> </ItemGroup> </Project>
سپس یک نمونه از MS Office automation را توسط اشیاء COM آن به صورت زیر میتوان پیاده سازی کرد:
using System; using System.Reflection; using Excel = Microsoft.Office.Interop.Excel; namespace ExcelDemo { class Program { public static void Main(string[] args) { Excel.Application excel; Excel.Workbook workbook; Excel.Worksheet sheet; Excel.Range range; try { // Start Excel and get Application object. excel = new Excel.Application(); excel.Visible = true; // Get a new workbook. workbook = excel.Workbooks.Add(Missing.Value); sheet = (Excel.Worksheet)workbook.ActiveSheet; // Add table headers going cell by cell. sheet.Cells[1, 1] = "First Name"; sheet.Cells[1, 2] = "Last Name"; sheet.Cells[1, 3] = "Full Name"; sheet.Cells[1, 4] = "Salary"; // Format A1:D1 as bold, vertical alignment = center. sheet.get_Range("A1", "D1").Font.Bold = true; sheet.get_Range("A1", "D1").VerticalAlignment = Excel.XlVAlign.xlVAlignCenter; // Create an array to multiple values at once. string[,] saNames = new string[5, 2]; saNames[0, 0] = "John"; saNames[0, 1] = "Smith"; saNames[1, 0] = "Tom"; saNames[1, 1] = "Brown"; saNames[2, 0] = "Sue"; saNames[2, 1] = "Thomas"; saNames[3, 0] = "Jane"; saNames[3, 1] = "Jones"; saNames[4, 0] = "Adam"; saNames[4, 1] = "Johnson"; // Fill A2:B6 with an array of values (First and Last Names). sheet.get_Range("A2", "B6").Value2 = saNames; // Fill C2:C6 with a relative formula (=A2 & " " & B2). range = sheet.get_Range("C2", "C6"); range.Formula = "=A2 & \" \" & B2"; // Fill D2:D6 with a formula(=RAND()*100000) and apply format. range = sheet.get_Range("D2", "D6"); range.Formula = "=RAND()*100000"; range.NumberFormat = "$0.00"; // AutoFit columns A:D. range = sheet.get_Range("A1", "D1"); range.EntireColumn.AutoFit(); // Make sure Excel is visible and give the user control // of Microsoft Excel's lifetime. excel.Visible = true; excel.UserControl = true; } catch (Exception e) { Console.WriteLine($"Error: {e.Message} Line: {e.Source}"); } } } }
How to automate Microsoft Excel from Microsoft Visual C#.NET
جدول AppUserClaims
جدول AppUserClaims، جزو جداول اصلی ASP.NET Core Identity است و هدف آن ذخیرهی اطلاعات ویژهی کاربران و بازیابی سادهتر آنها از طریق کوکیهای آنها است (همانند User.Identity.Name). زمانیکه کاربری به سیستم وارد میشود، بر اساس UserId او، تمام رکوردهای User Claims متعلق به او از این جدول واکشی شده و به صورت خودکار به کوکی او اضافه میشوند.
در پروژهی DNT Identity از این جدول استفاده نمیشود. چون اطلاعات User Claims مورد نیاز آن، هم اکنون در جدول AppUsers موجود هستند. به همین جهت افزودن این نوع User Claimها به جدول AppUserClaims، به ازای هر کاربر، کاری بیهوده است. سناریویی که استفادهی از این جدول را با مفهوم میکند، ذخیره سازی تنظیمات ویژهی هرکاربر است (خارج از فیلدهای جدول کاربران). برای مثال اگر سایتی را چندزبانه طراحی کردید، میتوانید یک User Claim سفارشی جدید را برای این منظور ایجاد و زبان انتخابی کاربر را به عنوان یک رکورد جدید مخصوص آن در این جدول ویژه ثبت کنید. مزیت آن این است که واکشی و افزوده شدن اطلاعات آن به کوکی شخص، به صورت خودکار توسط فریم ورک صورت گرفته و در حین مرور صفحات توسط کاربر، دیگر نیازی نیست تا اطلاعات زبان انتخابی او را از بانک اطلاعاتی واکشی کرد.
بنابراین برای ذخیره سازی تنظیمات با کارآیی بالای ویژهی هرکاربر، جدول جدیدی را ایجاد نکنید. جدول User Claim برای همین منظور درنظر گرفته شدهاست و پردازش اطلاعات آن توسط فریم ورک صورت میگیرد.
ASP.NET Core Identity چگونه اطلاعات جدول AppUserClaims را پردازش میکند؟
ASP.NET Identity Core در حین لاگین کاربر به سیستم، از سرویس SignInManager خودش استفاده میکند که با نحوهی سفارشی سازی آن پیشتر در قسمت دوم این سری آشنا شدیم. سرویس SignInManager پس از لاگین شخص، از یک سرویس توکار دیگر این فریم ورک به نام UserClaimsPrincipalFactory جهت واکشی اطلاعات User Claims و همچنین Role Claims و افزودن آنها به کوکی رمزنگاری شدهی شخص، استفاده میکند.
بنابراین اگر قصد افزودن User Claim سفارشی دیگری را داشته باشیم، میتوان همین سرویس توکار UserClaimsPrincipalFactory را سفارشی سازی کرد (بجای اینکه الزاما رکوردی را به جدول AppUserClaims اضافه کنیم).
اطلاعات جالبی را هم میتوان از پیاده سازی متد CreateAsync آن استخراج کرد:
public virtual async Task<ClaimsPrincipal> CreateAsync(TUser user)
2) userName شخص پس از لاگین از طریق User Claims ایی با نوع Options.ClaimsIdentity.UserNameClaimType به کوکی او اضافه میشود.
3) security stamp او (آخرین بار تغییر اطلاعات اکانت کاربر) نیز یک Claim پیشفرض است.
4) اگر نقشهایی به کاربر انتساب داده شده باشند، تمام این نقشها واکشی شده و به عنوان یک Claim جدید به کوکی او اضافه میشوند.
5) اگر یک نقش منتسب به کاربر دارای Role Claim باشد، این موارد نیز واکشی شده و به کوکی او به عنوان یک Claim جدید اضافه میشوند. در ASP.NET Identity Core نقشها نیز میتوانند Claim داشته باشند (امکان پیاده سازی سطوح دسترسی پویا).
بنابراین حداقل مدیریت Claims این 5 مورد خودکار است و اگر برای مثال نیاز به Id کاربر لاگین شده را داشتید، نیازی نیست تا آنرا از بانک اطلاعاتی واکشی کنید. چون این اطلاعات هم اکنون در کوکی او موجود هستند.
سفارشی سازی کلاس UserClaimsPrincipalFactory جهت افزودن User Claims سفارشی
تا اینجا دریافتیم که کلاس UserClaimsPrincipalFactory کار مدیریت Claims پیشفرض این فریم ورک را برعهده دارد. در ادامه از این کلاس ارث بری کرده و متد CreateAsync آنرا جهت افزودن Claims سفارشی خود بازنویسی میکنیم. این پیاده سازی سفارشی را در کلاس ApplicationClaimsPrincipalFactory میتوانید مشاهده کنید:
public override async Task<ClaimsPrincipal> CreateAsync(User user) { var principal = await base.CreateAsync(user).ConfigureAwait(false); addCustomClaims(user, principal); return principal; } private static void addCustomClaims(User user, IPrincipal principal) { ((ClaimsIdentity) principal.Identity).AddClaims(new[] { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), ClaimValueTypes.Integer), new Claim(ClaimTypes.GivenName, user.FirstName ?? string.Empty), new Claim(ClaimTypes.Surname, user.LastName ?? string.Empty), new Claim(PhotoFileName, user.PhotoFileName ?? string.Empty, ClaimValueTypes.String), }); }
برای مثال نام، نام خانوادگی و نام تصویر شخص به صورت Claimهایی جدید به کوکی او اضافه میشوند. در این حالت دیگر نیازی نیست تا به ازای هر کاربر، جدول AppUserClaims را ویرایش کرد و اطلاعات جدیدی را افزود و یا تغییر داد. همینقدر که کاربر به سیستم لاگین کند، شیء User او به متد Create کلاس UserClaimsPrincipalFactory ارسال میشود. به این ترتیب میتوان به تمام خواص این کاربر دسترسی یافت و در صورت نیاز آنها را به صورت Claimهایی به کوکی او افزود.
پس از تدارک کلاس ApplicationClaimsPrincipalFactory، تنها کاری را که باید در جهت معرفی و جایگرینی آن انجام داد، تغییر ذیل در کلاس IdentityServicesRegistry است:
services.AddScoped<IUserClaimsPrincipalFactory<User>, ApplicationClaimsPrincipalFactory>(); services.AddScoped<UserClaimsPrincipalFactory<User, Role>, ApplicationClaimsPrincipalFactory>();
چگونه به اطلاعات User Claims در سرویسهای برنامه دسترسی پیدا کنیم؟
برای دسترسی به اطلاعات User Claims نیاز به دسترسی به HttpContext جاری را داریم. در این مورد و تزریق سرویس IHttpContextAccessor جهت تامین آن، در مطلب «بررسی روش دسترسی به HttpContext در ASP.NET Core» پیشتر بحث شدهاست.
به علاوه در کلاس IdentityServicesRegistry، تزریق وابستگیهای سفارشیتری نیز صورت گرفتهاست:
services.AddScoped<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>()?.HttpContext?.User ?? ClaimsPrincipal.Current);
چگونه اطلاعات User Claims سفارشی را دریافت کنیم؟
برای کار سادهتر با Claims یک کلاس کمکی به نام IdentityExtensions به پروژه اضافه شدهاست و متدهایی مانند دو متد ذیل را میتوانید در آن مشاهده کنید:
public static string FindFirstValue(this ClaimsIdentity identity, string claimType) { return identity?.FindFirst(claimType)?.Value; } public static string GetUserClaimValue(this IIdentity identity, string claimType) { var identity1 = identity as ClaimsIdentity; return identity1?.FindFirstValue(claimType); }
برای نمونه متد GetUserDisplayName این کلاس کمکی، از همان Claims سفارشی که در کلاس ApplicationClaimsPrincipalFactory تعریف کردیم، اطلاعات خود را استخراج میکند و اگر در View ایی خواستید این اطلاعات را نمایش دهید، میتوانید بنویسید:
@User.Identity.GetUserDisplayName()
چگونه پس از ویرایش اطلاعات کاربر، اطلاعات کوکی او را نیز به روز کنیم؟
در پروژه قسمتی وجود دارد جهت ویرایش اطلاعات کاربران (UserProfileController). اگر کاربری برای مثال نام و نام خانوادگی خود را ویرایش کرد، میخواهیم بلافاصله متد GetUserDisplayName اطلاعات صحیح و به روزی را از کوکی او دریافت کند. برای اینکار یا میتوان او را وادار به لاگین مجدد کرد (تا پروسهی رسیدن به متد CreateAsync کلاس ApplicationClaimsPrincipalFactory طی شود) و یا روش بهتری نیز وجود دارد:
// reflect the changes, in the current user's Identity cookie await _signInManager.RefreshSignInAsync(user).ConfigureAwait(false);
کدهای کامل این سری را در مخزن کد DNT Identity میتوانید ملاحظه کنید.