جهت اجرای قسمت «استفاده از jsSHA به صورت typed » : «نکتهای در مورد رفع مشکل «typings ERR! caused by connect ECONNREFUSED 10.10.34.36:443» پس از نصب TypeScript 2.0»
جهت تکمیل این بحث
برای VS 2013 این اصلاح را هم که مرتبط با AngularJS 2.0 هست، نصب کنید (پس از نصب پلاگین آن). این اصلاحیهی ویژه برای VS 2015 درصورتیکه TypeScript 2.0.3 را نصب کرده باشید، نیازی نیست.
برای VS 2013 این اصلاح را هم که مرتبط با AngularJS 2.0 هست، نصب کنید (پس از نصب پلاگین آن). این اصلاحیهی ویژه برای VS 2015 درصورتیکه TypeScript 2.0.3 را نصب کرده باشید، نیازی نیست.
اشتراکها
پیش نمایش C# 7 با Mads Torgersen
با تشکر از پاسختون
درسته این به صورت پویا تولید میشه ولی شکل model ای که شما در این مطلب توضیح دادید با این چیزی که کد من تولید میکنه فرق میکنه
برای شما اول نام فیلد هست بعد نوع اون فیلد، در حالی که نحوه تولید داینامیک اینو نمیدونم چطوری باید باشه.
درسته این به صورت پویا تولید میشه ولی شکل model ای که شما در این مطلب توضیح دادید با این چیزی که کد من تولید میکنه فرق میکنه
برای شما اول نام فیلد هست بعد نوع اون فیلد، در حالی که نحوه تولید داینامیک اینو نمیدونم چطوری باید باشه.
model: { fields: { "Id": { type: "number" }, //تعیین نوع فیلد برای جستجوی پویا مهم است "Name": { type: "string" }, "IsAvailable": { type: "boolean" }, "Price": { type: "number" } } } }
سلام.امکان داره یه مثال در مورد نحوه استفاده از قطعه کد زیر که مربوط به خواص سایه ای میشه بزنید.
به عنوان مثال سعی کردم به شکل زیر از این قطعه کد استفاده کنم ولی با پیغام
public static readonly Func<object, DateTimeOffset?> EFPropertyCreatedDateTime = entity => EF.Property<DateTimeOffset?>(entity, CreatedDateTime);
"The EF.Property<T> method may only be used within LINQ queries"
سیستم متوقف میشه.
var persons = context.Persons .Where(x => AuditableShadowProperties.EFPropertyCreatedDateTime(x) == DateTimeOffset.UtcNow) .ToList();
صفحات خروجی وب سایت زمانی که رندر شده و در مرورگر نشان داده میشود شامل فواصل اضافی است که تاثیری در نمایش سایت نداشته و صرفا این کاراکترها فضای اضافی اشغال میکنند. با حذف این کاراکترهای اضافی میتوان تا حد زیادی صفحه را کم حجم کرد. برای این کار در ASP.NET Webform کارهایی (^ ) انجام شده است.
روال کار به این صورت بوده که قبل از رندر شدن صفحه در سمت سرور خروجی نهایی بررسی شده و با استفاده از عبارات با قاعده الگوهای مورد نظر لیست شده و سپس حذف میشوند و در نهایت خروجی مورد نظر حاصل خواهد شد. برای راحتی کار و عدم نوشتن این روال در تمامی صفحات میتواند در مستر پیج این عمل را انجام داد. مثلا:
در هر صفحه رویدادی به نام Render وجود دارد که خروجی نهایی را میتوان در آن تغییر داد. همانگونه که مشاهده میشود عملیات یافتن و حذف فضاهای خالی در این متد انجام میشود.
این عمل در ASP.NET Webform به آسانی انجام شده و باعث حذف فضاهای خالی در خروجی صفحه میشود.
برای انجام این عمل در ASP.NET MVC روال کار به این صورت نیست و نمیتوان مانند ASP.NET Webform عمل کرد.
چون در MVC از ViewPage استفاده میشود و ما مستقیما به خروجی آن دسترسی نداریم یک روش این است که میتوانیم یک کلاس برای ViewPage تعریف کرده و رویداد Write آن را تحریف کرده و مانند مثال بالا فضای خالی را در خروجی حذف کرد. البته برای استفاده باید کلاس ایجاد شده را به عنوان فایل پایه جهت ایجاد صفحات در MVC فایل web.config معرفی کنیم. این روش در اینجا به وضوح شرح داده شده است.
اما هدف ما پیاده سازی با استفاده از اکشن فیلتر هاست. برای پیاده سازی ایتدا یک اکشن فیلتر به نام CompressAttribute تعریف میکنیم مانند زیر:
در این کلاس فشرده سازی (gzip و deflate نیز اعمال شده است) در متد OnActionExecuting ابتدا در خط 24 بررسی میشود که آیا درخواست رسیده gzip را پشتیبانی میکند یا خیر. در صورت پشتیبانی خروجی صفحه را با استفاده از gzip یا deflate فشرده سازی میکند. تا اینجای کار ممکن است مورد نیاز ما نباشد. اصل کار ما (حذف کردن فضاهای خالی) در خط 42 اعمال شده است. در واقع برای حذف فضاهای خالی باید یک کلاس که از Stream ارث بری دارد تعریف شده و خروجی کلاس مورد نظر به فیلتر درخواست ما اعمال شود.
در کلاس WhitespaceFilter با تحریف متد Write الگوهای فضای خالی موجود در درخواست یافت شده و آنها را حذف میکنیم. در نهایت خروجی این کلاس که از نوع استریم است به ویژگی فیلتر صفحه اعمال میشود.
برای معرفی فیلتر تعریف شده میتوان در فایل Global.asax در رویداد Application_Start به صورت زیر فیلتر مورد نظر را به فیلترهای MVC اعمال کرد.
برای آشنایی بیشتر فیلترها در ASP.NET MVC را مطالعه نمایید.
پ.ن: جهت سهولت، در این کلاس ها، صفحات فشرده سازی و همزمان فضاهای خالی آنها حذف شده است.
روال کار به این صورت بوده که قبل از رندر شدن صفحه در سمت سرور خروجی نهایی بررسی شده و با استفاده از عبارات با قاعده الگوهای مورد نظر لیست شده و سپس حذف میشوند و در نهایت خروجی مورد نظر حاصل خواهد شد. برای راحتی کار و عدم نوشتن این روال در تمامی صفحات میتواند در مستر پیج این عمل را انجام داد. مثلا:
private static readonly Regex RegexBetweenTags = new Regex(@">\s+<", RegexOptions.Compiled); private static readonly Regex RegexLineBreaks = new Regex(@"\r\s+", RegexOptions.Compiled); protected override void Render(HtmlTextWriter writer) { using (var htmlwriter = new HtmlTextWriter(new System.IO.StringWriter())) { base.Render(htmlwriter); var html = htmlwriter.InnerWriter.ToString(); html = RegexBetweenTags.Replace(html, "> <"); html = RegexLineBreaks.Replace(html, string.Empty); html = html.Replace("//<![CDATA[", "").Replace("//]]>", ""); html = html.Replace("// <![CDATA[", "").Replace("// ]]>", ""); writer.Write(html.Trim()); } }
این عمل در ASP.NET Webform به آسانی انجام شده و باعث حذف فضاهای خالی در خروجی صفحه میشود.
برای انجام این عمل در ASP.NET MVC روال کار به این صورت نیست و نمیتوان مانند ASP.NET Webform عمل کرد.
چون در MVC از ViewPage استفاده میشود و ما مستقیما به خروجی آن دسترسی نداریم یک روش این است که میتوانیم یک کلاس برای ViewPage تعریف کرده و رویداد Write آن را تحریف کرده و مانند مثال بالا فضای خالی را در خروجی حذف کرد. البته برای استفاده باید کلاس ایجاد شده را به عنوان فایل پایه جهت ایجاد صفحات در MVC فایل web.config معرفی کنیم. این روش در اینجا به وضوح شرح داده شده است.
اما هدف ما پیاده سازی با استفاده از اکشن فیلتر هاست. برای پیاده سازی ایتدا یک اکشن فیلتر به نام CompressAttribute تعریف میکنیم مانند زیر:
using System; using System.IO; using System.IO.Compression; using System.Text; using System.Text.RegularExpressions; using System.Web; using System.Web.Mvc; namespace PWS.Common.ActionFilters { public class CompressAttribute : ActionFilterAttribute { #region Methods (2) // Public Methods (1) /// <summary> /// Called by the ASP.NET MVC framework before the action method executes. /// </summary> /// <param name="filterContext">The filter context.</param> public override void OnActionExecuting(ActionExecutingContext filterContext) { var response = filterContext.HttpContext.Response; if (IsGZipSupported(filterContext.HttpContext.Request)) { String acceptEncoding = filterContext.HttpContext.Request.Headers["Accept-Encoding"]; if (acceptEncoding.Contains("gzip")) { response.Filter = new GZipStream(response.Filter, CompressionMode.Compress); response.AppendHeader("Content-Encoding", "gzip"); } else { response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress); response.AppendHeader("Content-Encoding", "deflate"); } } // Allow proxy servers to cache encoded and unencoded versions separately response.AppendHeader("Vary", "Content-Encoding"); //حذف فضاهای خالی
response.Filter = new WhitespaceFilter(response.Filter); } // Private Methods (1) /// <summary> /// Determines whether [is G zip supported] [the specified request]. /// </summary> /// <param name="request">The request.</param> /// <returns></returns> private Boolean IsGZipSupported(HttpRequestBase request) { String acceptEncoding = request.Headers["Accept-Encoding"]; if (acceptEncoding == null) return false; return !String.IsNullOrEmpty(acceptEncoding) && acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate"); } #endregion Methods } /// <summary> /// Whitespace Filter /// </summary> public class WhitespaceFilter : Stream { #region Fields (3) private readonly Stream _filter; /// <summary> /// /// </summary> private static readonly Regex RegexAll = new Regex(@"\s+|\t\s+|\n\s+|\r\s+", RegexOptions.Compiled); /// <summary> /// /// </summary> private static readonly Regex RegexTags = new Regex(@">\s+<", RegexOptions.Compiled); #endregion Fields #region Constructors (1) /// <summary> /// Initializes a new instance of the <see cref="WhitespaceFilter" /> class. /// </summary> /// <param name="filter">The filter.</param> public WhitespaceFilter(Stream filter) { _filter = filter; } #endregion Constructors #region Properties (5) //methods that need to be overridden from stream /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports reading. /// </summary> /// <returns>true if the stream supports reading; otherwise, false.</returns> public override bool CanRead { get { return true; } } /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports seeking. /// </summary> /// <returns>true if the stream supports seeking; otherwise, false.</returns> public override bool CanSeek { get { return true; } } /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports writing. /// </summary> /// <returns>true if the stream supports writing; otherwise, false.</returns> public override bool CanWrite { get { return true; } } /// <summary> /// When overridden in a derived class, gets the length in bytes of the stream. /// </summary> /// <returns>A long value representing the length of the stream in bytes.</returns> public override long Length { get { return 0; } } /// <summary> /// When overridden in a derived class, gets or sets the position within the current stream. /// </summary> /// <returns>The current position within the stream.</returns> public override long Position { get; set; } #endregion Properties #region Methods (6) // Public Methods (6) /// <summary> /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. Instead of calling this method, ensure that the stream is properly disposed. /// </summary> public override void Close() { _filter.Close(); } /// <summary> /// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device. /// </summary> public override void Flush() { _filter.Flush(); } /// <summary> /// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. /// </summary> /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param> /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param> /// <param name="count">The maximum number of bytes to be read from the current stream.</param> /// <returns> /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. /// </returns> public override int Read(byte[] buffer, int offset, int count) { return _filter.Read(buffer, offset, count); } /// <summary> /// When overridden in a derived class, sets the position within the current stream. /// </summary> /// <param name="offset">A byte offset relative to the <paramref name="origin" /> parameter.</param> /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin" /> indicating the reference point used to obtain the new position.</param> /// <returns> /// The new position within the current stream. /// </returns> public override long Seek(long offset, SeekOrigin origin) { return _filter.Seek(offset, origin); } /// <summary> /// When overridden in a derived class, sets the length of the current stream. /// </summary> /// <param name="value">The desired length of the current stream in bytes.</param> public override void SetLength(long value) { _filter.SetLength(value); } /// <summary> /// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. /// </summary> /// <param name="buffer">An array of bytes. This method copies <paramref name="count" /> bytes from <paramref name="buffer" /> to the current stream.</param> /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the current stream.</param> /// <param name="count">The number of bytes to be written to the current stream.</param> public override void Write(byte[] buffer, int offset, int count) { string html = Encoding.Default.GetString(buffer); //remove whitespace html = RegexTags.Replace(html, "> <"); html = RegexAll.Replace(html, " "); byte[] outdata = Encoding.Default.GetBytes(html); //write bytes to stream _filter.Write(outdata, 0, outdata.GetLength(0)); } #endregion Methods } }
در کلاس WhitespaceFilter با تحریف متد Write الگوهای فضای خالی موجود در درخواست یافت شده و آنها را حذف میکنیم. در نهایت خروجی این کلاس که از نوع استریم است به ویژگی فیلتر صفحه اعمال میشود.
برای معرفی فیلتر تعریف شده میتوان در فایل Global.asax در رویداد Application_Start به صورت زیر فیلتر مورد نظر را به فیلترهای MVC اعمال کرد.
GlobalFilters.Filters.Add(new CompressAttribute());
پ.ن: جهت سهولت، در این کلاس ها، صفحات فشرده سازی و همزمان فضاهای خالی آنها حذف شده است.
مطالب
آموزش TypeScript #4
در پستهای قبل با کلیات و primitive types در زبان TypeScript آشنا شدیم:
اما به صورت معمول سعی میشود هر ماژول در یک فایل جداگانه تعریف شود.
استفاده از چند ماژول در یک فایل به مرور، درک پروژه را سخت خواهد کرد و در
هنگام توسعه امکان برخورد با مشکل وجود خواهد داشت. برای مثال اگر یک فایل
به نام MyModule.ts داشته باشیم که یک ماژول به این نام را شامل شود بعد از
کامپایل یک فایل به نام MyModule.js ایجاد خواهد شد.
کلاس ها:
برای تعریف یک کلاس میتوانیم همانند دات نت از کلمه کلیدی class استفاده کنیم. بعد از تعریف کلاس میتوانیم متغیرها و توابع مورد نظر را در این کلاس قرار داده و تعریف کنیم.
نکته مهم و جالب قسمت بالا کلمه export است. export معادل public در دات نت
است و کلاس logger را قابل دسترس در خارج ماژول Utilities خواهد کرد. اگر
از export در هنگام تعریف کلاس استفاده نکنیم این کلاس فقط در سایر
کلاسهای تعریف شده در داخل همان ماژول قابل دسترس است.
تابع log که در کلاس بالا تعریف کردیم به صورت پیش فرض public یا عمومی است و نیاز به استفاده export نیست.
برای استفاده از کلاس بالا باید این کلمه کلیدی new استفاده کنیم.
برای تعریف سازنده برای کلاس بالا باید از کلمه کلیدی constructor استفاده نماییم:
استفاده از پارامترهای Rest
منظور از پارامترهای Rest یعنی در هنگام فراخوانی توابع محدودیتی برای تعداد پارامترها نیست که معادل params در دات نت است. برای تعریف این گونه پارامترهاکافیست به جای params از ... استفاده نماییم.
تعریف توابع خصوصی
در TypeScript امکان توابع خصوصی با کلمه کلیدی private امکان پذیر است. همانند دات نت با استفاده از کلمه کلیدی private میتوانیم کلاسی تعریف کنیم که فقط برای همان کلاس قابل دسترس باشد(به صورت پیش فرض توابع به صورت عمومی هستند).
از آن جا که تابع getTimeStamp به صورت خصوصی تعریف شده است در نتیجه امکان
استفاده از آن در خارج کلاس وجود ندارد. اگر سعی بر استفاده این تابع
داشته باشیم توسط کامپایلر با یک warning مواجه خواهیم شد.
و استفاده از این تابع بدون وهله سازی از کلاس :
Function Overload
همان گونه که در دات نت امکان overload کردن توابع میسر است در TypeScript هم این امکان وجود دارد.
ادامه دارد...
در این پست به مفاهیم شی گرایی در این زبان میپردازیم.
تعریف یک ماژول: برای تعریف یک ماژول باید از کلمه کلیدی module استفاده
کنید. یک ماژول معادل یک ظرف است برای نگهداری کلاسها و اینترفیسها و
سایر ماژول ها. کلاسها و اینترفیسها در TypeScript میتوانند به صورت
internal یا public باشند(به صورت پیش فرض internal است؛ یعنی فقط در همان ماژول قابل استفاده و فراخوانی است). هر چیزی که در داخل یک ماژول تعریف میشود
محدوده آن در داخل آن ماژول خواهد بود. اگر قصد توسعه یک پروژه در مقیاس
بزرگ را دارید میتوانید همانند دات نت که در آن امکان تعریف فضای نامهای
تودرتو امکان پذیر است در TypeScript نیز، ماژولهای تودرتو تعریف
کنید. برای مثال:
module MyModule1 { module MyModule2 { } }
کلاس ها:
برای تعریف یک کلاس میتوانیم همانند دات نت از کلمه کلیدی class استفاده کنیم. بعد از تعریف کلاس میتوانیم متغیرها و توابع مورد نظر را در این کلاس قرار داده و تعریف کنیم.
module Utilities { export class Logger { log(message: string): void{ if(typeofwindow.console !== 'undefined') { window.console.log(message); } } } }
تابع log که در کلاس بالا تعریف کردیم به صورت پیش فرض public یا عمومی است و نیاز به استفاده export نیست.
برای استفاده از کلاس بالا باید این کلمه کلیدی new استفاده کنیم.
window.onload = function() { varlogger = new Utilities.Logger(); logger.log('Logger is loaded'); };
export class Logger{ constructor(private num: number) { }
با کمی دقت متوجه تعریف متغیر num به صورت private خواهید شد که برخلاف انتظار ما در زبانهای دات نتی است. بر خلاف دات نت در زبان TypeScript، دسترسی به متغیر تعریف شده در سازنده با کمک اشاره گر this در هر جای کلاس ممکن میباشد. در نتیجه نیازی به تعریف متغیر جدید و پاس دادن مقادیر این متغیرها به این فیلدها نمیباشد.
اگر به تابع log دقت کنید خواهید دید که یک پارامتر ورودی به نام message دارد که نوع آن string است. در ضمن Typescript از پارامترهای اختیاری( پارامتر با مقدار پیش فرض) نیز پشتیبانی میکند. مثال:
اگر به تابع log دقت کنید خواهید دید که یک پارامتر ورودی به نام message دارد که نوع آن string است. در ضمن Typescript از پارامترهای اختیاری( پارامتر با مقدار پیش فرض) نیز پشتیبانی میکند. مثال:
pad(num: number, len: number= 2, char: string= '0')
منظور از پارامترهای Rest یعنی در هنگام فراخوانی توابع محدودیتی برای تعداد پارامترها نیست که معادل params در دات نت است. برای تعریف این گونه پارامترهاکافیست به جای params از ... استفاده نماییم.
function addManyNumbers(...numbers: number[]) { var sum = 0; for(var i = 0; i < numbers.length; i++) { sum += numbers[i]; } returnsum; } var result = addManyNumbers(1,2,3,5,6,7,8,9);
در TypeScript امکان توابع خصوصی با کلمه کلیدی private امکان پذیر است. همانند دات نت با استفاده از کلمه کلیدی private میتوانیم کلاسی تعریف کنیم که فقط برای همان کلاس قابل دسترس باشد(به صورت پیش فرض توابع به صورت عمومی هستند).
module Utilities { Export class Logger { log(message: string): void{ if(typeofwindow.console !== 'undefined') { window.console.log(this.getTimeStamp() + ' -'+ message); window.console.log(this.getTimeStamp() + ' -'+ message); } } private getTimeStamp(): string{ var now = newDate(); return now.getHours() + ':'+ now.getMinutes() + ':'+ now.getSeconds() + ':'+ now.getMilliseconds(); } } }
یک نکته مهم این است که کلمه private فقط برای توابع و متغیرها قابل استفاده است.
تعریف توابع static:
در TypeScript امکان تعریف توابع static وجود دارد. همانند دات نت باید از کلمه کلیدی static استفاده کنیم.
classFormatter { static pad(num: number, len: number, char: string): string{ var output = num.toString(); while(output.length < len) { output = char + output; } returnoutput; } } }
Formatter.pad(now.getSeconds(), 2, '0') +
همان گونه که در دات نت امکان overload کردن توابع میسر است در TypeScript هم این امکان وجود دارد.
static pad(num: number, len?: number, char?: string); static pad(num: string, len?: number, char?: string); static pad(num: any, len: number= 2, char: string= '0') { var output = num.toString(); while(output.length < len) { output = char + output; } returnoutput; }
ادامه دارد...
روشهای زیادی برای بازگشت چندین مقدار از یک متد وجود دارند؛ مانند استفادهی از آرایهها برای بازگشت اشیایی از یک جنس، ایجاد یک کلاس سفارشی با خواص متفاوت و استفاده از پارامترهای out و ref همانند روشهای متداول در C و ++C. در این بین روش دیگری نیز به نام Tuples از زمان NET 4.0. برای بازگشت چندین شیء با نوعهای مختلف، ارائه شدهاست که در C# 7 نحوهی تعریف و استفادهی از آنها بهبود قابل ملاحظهای یافتهاست.
Tuple چیست؟
هدف از کار با Tupleها، عدم تعریف یک کلاس جدید به همراه خواص آن، جهت بازگشت بیش از یک مقدار از یک متد، توسط وهلهای از این کلاس جدید میباشد. برای مثال اگر بخواهیم از متدی، دو مقدار شهر و ناحیه را بازگشت دهیم، یک روش آن، ایجاد کلاس مکان زیر است:
و سپس، وهله سازی و بازگشت آن:
اما توسط Tuples، بدون نیاز به تعریف یک کلاس جدید، باز هم میتوان به همین دو خروجی، دسترسی یافت:
مشکلات نوع Tuple در نگارشهای قبلی دات نت
هرچند Tuples از زمان دات نت 4 در دسترس هستند، اما دارای این کمبودها و مشکلات میباشند:
الف) پارامترهای خروجی آنها ثابت و با نامهایی مانند Item1، Item2 و امثال آن هستند که در حین استفاده، به علت ضعف نامگذاری، کاربرد آنها دقیقا مشخص نیست و کاملا بیمعنا هستند:
ب) Reference Type هستند (کلاس هستند) و در زمان وهله سازی، میزان مصرف حافظهی بیشتری را نسبت به Value Types (معادل Tuples در C# 7) دارند.
ج) Tuples در دات نت 4، صرفا یک کتابخانهی اضافه شدهی به فریم ورک بوده و زبانهای دات نتی، پشتیبانی توکاری را از آنها جهت بهبود و یا ساده سازی تعریف آنها، ارائه نمیدهند.
ایجاد Tuples در C# 7
برای ایجاد Tuples در سی شارپ 7، از پرانتزها به همراه ذکر نام و نوع پارامترها استفاده میشود.
در مثال فوق، یک Tuple ایجاد شدهاست و در آن مقدار 3 به x1 و مقدار "one" به s1 انتساب داده شدهاند. به این عملیات deconstruction هم میگویند.
دسترسی به این مقادیر نیز همانند متغیرهای معمولی است.
اگر سعی کنیم این قطعه کد را کامپایل نمائیم، با خطای ذیل متوقف خواهیم شد:
برای رفع این مشکل نیاز است بستهی نیوگت ذیل را نیز نصب کرد:
تعاریف متغیرهای بازگشتی، خارج از پرانتزها هم میتوانند صورت گیرند:
بازگشت Tuples از متدها
متد ذیل، دو خروجی نتیجه و باقیماندهی تقسیم دو عدد صحیح را باز میگرداند:
برای این منظور، نوع خروجی متد به صورت (int, int) و همچنین مقدار بازگشتی نیز به صورت یک Tuple از نتیجه و باقیماندهی تقسیم، تعریف شدهاست.
در ادامه نحوهی استفادهی از این متد را مشاهده میکنید:
در اینجا امکان استفادهی از var نیز برای تعریف نوع متغیرهای دریافتی از یک Tuple نیز وجود دارد و کامپایلر به صورت خودکار نوع آنها را بر اساس نوع خروجی tuple مشخص میکند:
و یا حتی چون نوع var پارامترها در اینجا یکی است و در هر دو حالت به int اشاره میکند، میتوان این var را در خارج از پرانتز هم قرار داد:
و یا برای نمونه متد GetHumanData دات نت 4 ابتدای بحث را به صورت ذیل میتوان در C# 7 بازنویسی کرد:
و سپس به نحو واضحتری از آن استفاده نمود؛ بدون استفادهی اجباری از Item1 و غیره (هرچند هنوز هم میتوان از آنها استفاده کرد):
پشت صحنهی Tuples در C# 7
همانطور که عنوان شد، برای اینکه بتوانید قطعه کدهای فوق را کامپایل کنید، نیاز به بستهی نیوگت System.ValueTuple است. در حقیقت کامپایلر خروجی متد فوق را به نحو ذیل تفسیر میکند:
برای مثال قطعه کد
توسط کامپایلر به قطعه کد ذیل ترجمه میشود:
- برخلاف نگارشهای پیشین دات نت که Tuples در آنها reference type بودند، این ValueTuple یک struct است و به همین جهت سربار تخصیص حافظهی کمتری را به همراه داشته و از لحاظ کارآیی و میزان مصرف حافظه بهینهتر عمل میکند.
- همچنین در اینجا محدودیتی از لحاظ تعداد پارامترهای ذکر شدهی در یک Tuple وجود ندارد.
در اینجا هم مانند قبل (دات نت 4) 8 آیتم را میتوان تعریف کرد؛ اما چون آخرین آیتم ValueTuple تعریف شده نیز یک Tuple است، در عمل محدودیتی از نظر تعداد پارامتر نخواهیم داشت.
مفهوم Tuple Literals
همانند نگارشهای پیشین دات نت، خروجی یک Tuple را میتوان به یک متغیر از نوع var و یا ValueType نیز نسبت داد:
در این حالت برای دسترسی به مقادیر Tuple همانند قبل باید از فیلدهای Item1 و Item2 و ... استفاده کرد.
به علاوه در سی شارپ 7 میتوان برای اعضای یک Tuple نام نیز تعریف کرد که به آنها Tuple literals گویند:
در این حالت زمانیکه Tuple به یک متغیر از نوع var نسبت داده میشود، میتوان به خروجی آن بر اساس نامهای اعضای Tuple، بجای ذکر Item1 و ... دسترسی یافت که خوانایی بیشتری دارند.
و یا هنگام تعریف نوع خروجی، میتوان نام پارامترهای متناظر را نیز ذکر کرد که به آن named elements هم میگویند:
و نمونهای از کاربرد آن به صورت ذیل است که در اینجا خروجی Tuple صرفا به یک متغیر از نوع var نسبت داده شدهاست و توسط نام پارامترهای خروجی متد، میتوان به اعضای Tuple دسترسی یافت.
مفهوم Deconstructing Tuples
مفهوم deconstruction که در ابتدای بحث عنوان شد صرفا مختص به Tuples نیست. در C# 7 میتوان مشخص کرد که چگونه یک نوع خاص، به اجزای آن تجزیه شود. برای مثال کلاس شخص ذیل را درنظر بگیرید:
- در اینجا یک متد جدید را به نام Deconstruct مشاهده میکنید. کار این متد جدید که توسط کامپایلر استفاده خواهد شد، ارائهی روشی است برای «تجزیهی» یک نوع، به یک Tuple. متد Deconstruct تعریف شدهی در اینجا توسط پارامترهایی از نوع out، دو خروجی را مشخص میکنند. امکان تعریف این متد ویژه، به صورتیکه یک Tuple را بازگرداند، وجود ندارد.
- علت تعریف این دو خروجی هم به constructor و یا سازندهی کلاس بر میگردد که دو ورودی را دریافت میکند. اگر یک کلاس چندین سازنده داشته باشد، به همان تعداد میتوان متد Deconstruct تعریف کرد؛ به همراه خروجیهایی متناظر با نوع پارامترهای سازندهها.
- علت استفادهی از نوع خروجی out نیز این است که در #C نمیتوان چندین overload را صرفا بر اساس نوع خروجیهای متفاوت متدها تعریف کرد.
- متد Deconstruct به صورت خودکار در زمان تجزیهی یک شیء به یک tuple فراخوانی میشود. در مثال زیر، شیء p1 به یک Tuple تجزیه شدهاست و این تجزیه بر اساس متد Deconstruct این کلاس مفهوم پیدا میکند:
امکان تعریف متد Deconstruct، به صورت یک متد الحاقی
روش اول تعریف متد ویژهی Deconstruct را در مثال قبل، در داخل کلاس اصلی مشاهده کردید. روش دیگر آن، استفادهی از متدهای الحاقی است که در این مورد خاص نیز مجاز است:
در اینجا کلاس مستطیل دارای سازندهای با دو پارامتر است؛ اما متد Deconstruct آن به صورت یک متد الحاقی، خارج از کلاس اصلی تعریف شدهاست.
اکنون امکان انتساب وهلهای از این کلاس به یک Tuple وجود دارد:
امکان جایگزین کردن Anonymous types با Tuples
قطعه کد ذیل را در نظر بگیرید:
در اینجا خروجی LINQ تهیه شده یک لیست anonymously typed است؛ با محدودیتهایی مانند عدم امکان استفادهی از خروجی آن در سایر اسمبلیها. این نوعهای ویژه تنها محدود هستند به همان اسمبلی که در آن تعریف میشوند. اما در C# 7 میتوان قطعه کد فوق را با Tuples به صورت ذیل بازنویسی کرد که این محدودیتها را هم ندارد (با هدف به حداقل رساندن تعداد ViewModelهای تعریفی یک برنامه):
سایر کاربردهای Tuples
از Tuples صرفا برای تعریف چندین خروجی از یک متد استفاده نمیشود. در ذیل نحوهی استفادهی از آنها را جهت تعریف کلید ترکیبی یک شیء دیکشنری و یا استفادهی از آنها را در آرگومان جنریک یک متد async هم مشاهده میکنید:
Tuple چیست؟
هدف از کار با Tupleها، عدم تعریف یک کلاس جدید به همراه خواص آن، جهت بازگشت بیش از یک مقدار از یک متد، توسط وهلهای از این کلاس جدید میباشد. برای مثال اگر بخواهیم از متدی، دو مقدار شهر و ناحیه را بازگشت دهیم، یک روش آن، ایجاد کلاس مکان زیر است:
public class Location { public string City { get; set; } public string State { get; set; } public Location(string city, string state) { City = city; State = state; } }
var location = new Location("Lake Charles","LA");
var location = new Tuple<string,string>("Lake Charles","LA"); // Print out the address var address = $"{location.Item1}, {location.Item2}";
مشکلات نوع Tuple در نگارشهای قبلی دات نت
هرچند Tuples از زمان دات نت 4 در دسترس هستند، اما دارای این کمبودها و مشکلات میباشند:
static Tuple<int, string, string> GetHumanData() { return Tuple.Create(10, "Marcus", "Miller"); }
var data = GetHumanData(); Console.WriteLine("What is this value {0} or this {1}", data.Item1, data.Item3);
ج) Tuples در دات نت 4، صرفا یک کتابخانهی اضافه شدهی به فریم ورک بوده و زبانهای دات نتی، پشتیبانی توکاری را از آنها جهت بهبود و یا ساده سازی تعریف آنها، ارائه نمیدهند.
ایجاد Tuples در C# 7
برای ایجاد Tuples در سی شارپ 7، از پرانتزها به همراه ذکر نام و نوع پارامترها استفاده میشود.
(int x1, string s1) = (3, "one"); Console.WriteLine($"{x1} {s1}");
دسترسی به این مقادیر نیز همانند متغیرهای معمولی است.
اگر سعی کنیم این قطعه کد را کامپایل نمائیم، با خطای ذیل متوقف خواهیم شد:
error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported
PM> install-package System.ValueTuple
تعاریف متغیرهای بازگشتی، خارج از پرانتزها هم میتوانند صورت گیرند:
int x2; string s2; (x2, s2) = (42, "two"); Console.WriteLine($"{x2} {s2}");
بازگشت Tuples از متدها
متد ذیل، دو خروجی نتیجه و باقیماندهی تقسیم دو عدد صحیح را باز میگرداند:
static (int, int) Divide(int x, int y) { int result = x / y; int reminder = x % y; return (result, reminder); }
در ادامه نحوهی استفادهی از این متد را مشاهده میکنید:
(int result, int reminder) = Divide(11, 3); Console.WriteLine($"{result} {reminder}");
در اینجا امکان استفادهی از var نیز برای تعریف نوع متغیرهای دریافتی از یک Tuple نیز وجود دارد و کامپایلر به صورت خودکار نوع آنها را بر اساس نوع خروجی tuple مشخص میکند:
(var result1, var reminder1) = Divide(11, 3); Console.WriteLine($"{result1} {reminder1}");
var (result1, reminder1) = Divide(11, 3);
و یا برای نمونه متد GetHumanData دات نت 4 ابتدای بحث را به صورت ذیل میتوان در C# 7 بازنویسی کرد:
static (int, string, string) GetHumanData() { return (10, "Marcus", "Miller"); }
(int Age, string FirstName, string LastName) results = GetHumanData(); Console.WriteLine(results.Age); Console.WriteLine(results.FirstName); Console.WriteLine(results.LastName);
پشت صحنهی Tuples در C# 7
همانطور که عنوان شد، برای اینکه بتوانید قطعه کدهای فوق را کامپایل کنید، نیاز به بستهی نیوگت System.ValueTuple است. در حقیقت کامپایلر خروجی متد فوق را به نحو ذیل تفسیر میکند:
ValueTuple<int, int> tuple1 = Divide(11, 3);
(int, int) n = (1,1); System.Console.WriteLine(n.Item1);
ValueTuple<int, int> n = new ValueTuple<int, int>(1, 1); System.Console.WriteLine(n.Item1);
- همچنین در اینجا محدودیتی از لحاظ تعداد پارامترهای ذکر شدهی در یک Tuple وجود ندارد.
(int,int,int,int,int,int,int,(int,int))
مفهوم Tuple Literals
همانند نگارشهای پیشین دات نت، خروجی یک Tuple را میتوان به یک متغیر از نوع var و یا ValueType نیز نسبت داد:
var tuple2 = ("Stephanie", 7); Console.WriteLine($"{tuple2.Item1}, {tuple2.Item2}");
به علاوه در سی شارپ 7 میتوان برای اعضای یک Tuple نام نیز تعریف کرد که به آنها Tuple literals گویند:
var tuple3 = (Name: "Matthias", Age: 6); Console.WriteLine($"{tuple3.Name} {tuple3.Age}");
و یا هنگام تعریف نوع خروجی، میتوان نام پارامترهای متناظر را نیز ذکر کرد که به آن named elements هم میگویند:
static (int radius, double area) CalculateAreaOfCircle(int radius) { return (radius, Math.PI * Math.Pow(radius, 2)); }
var circle = CalculateAreaOfCircle(2); Console.WriteLine($"A circle of radius, {circle.radius}," + $" has an area of {circle.area:N2}.");
مفهوم Deconstructing Tuples
مفهوم deconstruction که در ابتدای بحث عنوان شد صرفا مختص به Tuples نیست. در C# 7 میتوان مشخص کرد که چگونه یک نوع خاص، به اجزای آن تجزیه شود. برای مثال کلاس شخص ذیل را درنظر بگیرید:
class Person { private readonly string _firstName; private readonly string _lastName; public Person(string firstname, string lastname) { _firstName = firstname; _lastName = lastname; } public override String ToString() => $"{_firstName} {_lastName}"; public void Deconstruct(out string firstname, out string lastname) { firstname = _firstName; lastname = _lastName; } }
- علت تعریف این دو خروجی هم به constructor و یا سازندهی کلاس بر میگردد که دو ورودی را دریافت میکند. اگر یک کلاس چندین سازنده داشته باشد، به همان تعداد میتوان متد Deconstruct تعریف کرد؛ به همراه خروجیهایی متناظر با نوع پارامترهای سازندهها.
- علت استفادهی از نوع خروجی out نیز این است که در #C نمیتوان چندین overload را صرفا بر اساس نوع خروجیهای متفاوت متدها تعریف کرد.
- متد Deconstruct به صورت خودکار در زمان تجزیهی یک شیء به یک tuple فراخوانی میشود. در مثال زیر، شیء p1 به یک Tuple تجزیه شدهاست و این تجزیه بر اساس متد Deconstruct این کلاس مفهوم پیدا میکند:
var p1 = new Person("Katharina", "Nagel"); (string first, string last) = p1; Console.WriteLine($"{first} {last}");
امکان تعریف متد Deconstruct، به صورت یک متد الحاقی
روش اول تعریف متد ویژهی Deconstruct را در مثال قبل، در داخل کلاس اصلی مشاهده کردید. روش دیگر آن، استفادهی از متدهای الحاقی است که در این مورد خاص نیز مجاز است:
public class Rectangle { public Rectangle(int height, int width) { Height = height; Width = width; } public int Width { get; } public int Height { get; } } public static class RectangleExtensions { public static void Deconstruct(this Rectangle rectangle, out int height, out int width) { height = rectangle.Height; width = rectangle.Width; } }
اکنون امکان انتساب وهلهای از این کلاس به یک Tuple وجود دارد:
var r1 = new Rectangle(100, 200); (int height, int width) = r1; Console.WriteLine($"height: {height}, width: {width}");
امکان جایگزین کردن Anonymous types با Tuples
قطعه کد ذیل را در نظر بگیرید:
List<Employee> allEmployees = new List<Employee>() { new Employee { ID = 1L, Name = "Fred", Salary = 50000M }, new Employee { ID = 2L, Name = "Sally", Salary = 60000M }, new Employee { ID = 3L, Name = "George", Salary = 70000M } }; var wellPaid = from oneEmployee in allEmployees where oneEmployee.Salary > 50000M select new { EmpName = oneEmployee.Name, Income = oneEmployee.Salary };
var wellPaid = from oneEmployee in allEmployees where oneEmployee.Salary > 50000M orderby oneEmployee.Salary descending select (EmpName: oneEmployee.Name, Income: oneEmployee.Salary); var highestPaid = wellPaid.First().EmpName;
سایر کاربردهای Tuples
از Tuples صرفا برای تعریف چندین خروجی از یک متد استفاده نمیشود. در ذیل نحوهی استفادهی از آنها را جهت تعریف کلید ترکیبی یک شیء دیکشنری و یا استفادهی از آنها را در آرگومان جنریک یک متد async هم مشاهده میکنید:
public Task<(int index, T item)> FindAsync<T>(IEnumerable<T> input, Predicate<T> match) { var dictionary = new Dictionary<(int, int), string>(); throw new NotSupportedException(); }
طی این مقاله، نحوهی ذخیره سازی تنظیمات متغیر و پویای یک برنامه را به صورت Strongly Typed ارائه خواهم داد. برای این منظور، یک API را که از Lazy Loading ، Cache ، Reflection و Entity Framework بهره میگیرد، خواهیم ساخت.
برنامهی هدف ما که از این API استفاده میکند، یک اپلیکیشن Asp.net MVC است. قبل از شروع به ساخت API مورد نظر، یک دید کلی در مورد آنچه که قرار است در نهایت توسعه یابد، در زیر مشاهده میکنید:
public SettingsController(ISettings settings) { // example of saving _settings.General.SiteName = "دات نت تیپس"; _settings.Seo.HomeMetaTitle = ".Net Tips"; _settings.Seo.HomeMetaKeywords = "َAsp.net MVC,Entity Framework,Reflection"; _settings.Seo.HomeMetaDescription = "ذخیره تنظیمات برنامه"; _settings.Save(); }
همانطور که در کدهای بالا مشاهده میکنید، شی setting_ ما دارای دو پراپرتی فقط خواندنی بنامهای General و Seo است که شامل تنظیمات مورد نظر ما هستند و این دو کلاس از کلاس پایهی SettingBase ارث بری کردهاند. دو دلیل برای انجام این کار وجود دارد:
- تنظیمات به صورت گروه بندی شده در کنار هم قرار گرفتهاند و یافتن تنظیمات برای زمانی که نیاز به دسترسی به آنها داریم، راحتتر و سادهتر خواهد بود.
- به این شکل تنظیمات قابل دسترس در یک گروه، از دیتابیس بازیابی خواهند شد.
اصلا چرا باید این تنظیمات را در دیتابیس ذخیره کنیم؟
شاید فکر کنید چرا باید تنظیمات را در دیتابیس ذخیره کنیم در حالی که فایل web.config در درسترس است و میتوان توسط کلاس ConfigurationManager به اطلاعات آن دسترسی داشت.
جواب: دلیل این است که با تغییر فایل web.config، برنامهی وب شما ری استارت خواهد شد (چه زمانهایی یک برنامه Asp.net ری استارت میشود).
برای جلوگیری از این مساله، راه حل مناسب برای ذخیره سازی اطلاعاتی که نیاز به تغییر در زمان اجرا دارند، استفاده از از دیتابیس میباشد. در این مقاله از Entity Framework و پایگاه داده Sql Sever استفاده میکنم.
مراحل ساخت Setting API مورد نظر به شرح زیر است:
- ساخت یک Asp.net Web Application
- ساخت مدل Setting و افزودن آن به کانتکست Entity Framework
- ساخت کلاس SettingBase برای بازیابی و ذخیره سازی تنظیمات با رفلکشن
- ساخت کلاس GenralSettins و SeoSettings که از کلاس SettingBase ارث بری کردهاند.
- ساخت کلاس Settings به منظور مدیریت تمام انواع تنظیمات
یک برنامهی Asp.Net Web Application را از نوع MVC ایجاد کنید. تا اینجا مرحلهی اول ما به پایان رسید؛ چرا که ویژوال استودیو کارهای مورد نیاز ما را انجام خواهد داد.
لازم است مدل خود را به ApplicationDbContext موجود در فایل IdentityModels.cs معرفی کنیم. به شکل زیر:
namespace DynamicSettingAPI.Models { public interface IUnitOfWork { DbSet<Setting> Settings { get; set; } int SaveChanges(); } } public class ApplicationDbContext : IdentityDbContext<ApplicationUser>,IUnitOfWork { public DbSet<Setting> Settings { get; set; } public ApplicationDbContext() : base("DefaultConnection", throwIfV1Schema: false) { } public static ApplicationDbContext Create() { return new ApplicationDbContext(); } } namespace DynamicSettingAPI.Models { public class Setting { public string Name { get; set; } public string Type { get; set; } public string Value { get; set; } } }
لازم است تا متد OnModelCreating مربوط به ApplicationDbContext را نیز تحریف کنیم تا کانفیگ مربوط به مدل خود را نیز اعمال نمائیم.
protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Setting>() .HasKey(x => new { x.Name, x.Type }); modelBuilder.Entity<Setting>() .Property(x => x.Value) .IsOptional(); base.OnModelCreating(modelBuilder); }
ساختاری به شکل زیر مد نظر ماست:
کلاس SettingBase ما همچین ساختاری را خواهد داشت:
namespace DynamicSettingAPI.Service { public abstract class SettingsBase { //1 private readonly string _name; private readonly PropertyInfo[] _properties; protected SettingsBase() { //2 var type = GetType(); _name = type.Name; _properties = type.GetProperties(); } public virtual void Load(IUnitOfWork unitOfWork) { //3 get setting for this type name var settings = unitOfWork.Settings.Where(w => w.Type == _name).ToList(); foreach (var propertyInfo in _properties) { //get the setting from setting list var setting = settings.SingleOrDefault(s => s.Name == propertyInfo.Name); if (setting != null) { //4 set propertyInfo.SetValue(this, Convert.ChangeType(setting.Value, propertyInfo.PropertyType)); } } } public virtual void Save(IUnitOfWork unitOfWork) { //5 get all setting for this type name var settings = unitOfWork.Settings.Where(w => w.Type == _name).ToList(); foreach (var propertyInfo in _properties) { var propertyValue = propertyInfo.GetValue(this, null); var value = (propertyValue == null) ? null : propertyValue.ToString(); var setting = settings.SingleOrDefault(s => s.Name == propertyInfo.Name); if (setting != null) { // 6 update existing value setting.Value = value; } else { // 7 create new setting var newSetting = new Setting() { Name = propertyInfo.Name, Type = _name, Value = value, }; unitOfWork.Settings.Add(newSetting); } } } } }
متد Load وظیفهی واکشی تمام تنظیمات مربوط به Type و ست کردن مقادیر به دست آمده را به خصوصیات کلاس ما، برعهده دارد. کد زیر مقدار دریافتی از دیتابیس را به نوع داده پراپرتی مورد نظر تبدیل کرده و نتیجه را به عنوان Value پراپرتی ست میکند.
propertyInfo.SetValue(this, Convert.ChangeType(setting.Value, propertyInfo.PropertyType));
متد Save نیز وظیفهی ذخیره سازی مقادیر موجود در خصوصیات کلاس تنظیماتی را که از کلاس SettingBase ما به ارث برده است، به عهده دارد.
این متد دیتاهای موجود دردیتابیس را که متعلق به کلاس ارث برده مورد نظر ما هستند، واکشی میکند و در یک حلقه، اگر خصوصیتی در دیتابیس موجود بود، آن را ویرایش کرده وگرنه یک رکورد جدید را ثبت میکند.
کلاسهای تنظیمات شخصی سازی شده خود را به شکل زیر تعریف میکنیم :
public class GeneralSettings : SettingsBase { public string SiteName { get; set; } public string AdminEmail { get; set; } public bool RegisterUsersEnabled { get; set; } } public class GeneralSettings : SettingsBase { public string SiteName { get; set; } public string AdminEmail { get; set; } }
برای اینکه تنظیمات را به صورت یکجا داشته باشیم و Abstraction ای را برای استفاده از این API ارائه دهیم، یک اینترفیس و یک کلاس که اینترفیس مذکور را پیاده کرده است در نظر میگیریم:
public interface ISettings { GeneralSettings General { get; } SeoSettings Seo { get; } void Save(); } public class Settings : ISettings { // 1 private readonly Lazy<GeneralSettings> _generalSettings; // 2 public GeneralSettings General { get { return _generalSettings.Value; } } private readonly Lazy<SeoSettings> _seoSettings; public SeoSettings Seo { get { return _seoSettings.Value; } } private readonly IUnitOfWork _unitOfWork; public Settings(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; // 3 _generalSettings = new Lazy<GeneralSettings>(CreateSettings<GeneralSettings>); _seoSettings = new Lazy<SeoSettings>(CreateSettings<SeoSettings>); } public void Save() { // only save changes to settings that have been loaded if (_generalSettings.IsValueCreated) _generalSettings.Value.Save(_unitOfWork); if (_seoSettings.IsValueCreated) _seoSettings.Value.Save(_unitOfWork); _unitOfWork.SaveChanges(); } // 4 private T CreateSettings<T>() where T : SettingsBase, new() { var settings = new T(); settings.Load(_unitOfWork); return settings; } }
این اینترفیس مشخص میکند که ما به چه نوع تنظیماتی، دسترسی داریم و متد Save آن برای آپدیت کردن تنظیمات، در نظر گرفته شده است. هر کلاسی که از کلاس SettingBase ارث بری کرده را به صورت فیلد فقط خواندنی و با استفاده از کلاس Lazy درون آن ذکر میکنیم و به این صورت کلاس تنظیمات ما زمانی ساخته خواهد شد که برای اولین بار به آن دسترسی داشته باشیم.
متد CreateSetting وظیفهی لود دیتا را از دیتابیس، بر عهده دارد که برای این منظور، متد لود Type مورد نظر را فراخوانی میکند. این متد وقتی به کلاس تنظیمات مورد نظر برای اولین بار دسترسی پیدا کنیم، فراخوانی خواهد شد.
حتما امکان این وجود دارد که شما از امکان Caching هم بهره ببرید برای مثال همچین متد و سازندهای را در کلاس Settings در نظر بگیرید:
private readonly ICache _cache; public Settings(IUnitOfWork unitOfWork, ICache cache) { // ARGUMENT CHECKING SKIPPED FOR BREVITY _unitOfWork = unitOfWork; _cache = cache; _generalSettings = new Lazy<GeneralSettings>(CreateSettingsWithCache<GeneralSettings>); _seoSettings = new Lazy<SeoSettings>(CreateSettingsWithCache<SeoSettings>); } private T CreateSettingsWithCache<T>() where T : SettingsBase, new() { // this is where you would implement loading from ICache throw new NotImplementedException(); }
public ActionResult Index() { using (var uow = new ApplicationDbContext()) { var _settings = new Settings(uow); _settings.General.SiteName = "دات نت تیپس"; _settings.General.AdminEmail = "admin@gmail.com"; _settings.General.RegisterUsersEnabled = true; _settings.Seo.HomeMetaTitle = ".Net Tips"; _settings.Seo.MetaKeywords = "Asp.net MVC,Entity Framework,Reflection"; _settings.Seo.HomeMetaDescription = "ذخیره تنظیمات برنامه"; var settings2 = new Settings(uow); var output = string.Format("SiteName: {0} HomeMetaDescription: {1} MetaKeywords: {2} MetaTitle: {3} RegisterEnable: {4}", settings2.General.SiteName, settings2.Seo.HomeMetaDescription, settings2.Seo.MetaKeywords, settings2.Seo.HomeMetaTitle, settings2.General.RegisterUsersEnabled.ToString() ); return Content(output); } }
نکته: در پروژه ای که جدیدا در سایت ارائه دادهام و در حال تکمیل آن هستم، از بهبود یافتهی این مقاله استفاده میشود. حتی برای اسلاید شوهای سایت هم میشود از این روش استفاده کرد و از فرمت json بهره برد برای این منظور. حتما در پروژهی مذکور همچین امکانی را هم در نظر خواهم گرفتم.
پیشنها میکنم سورس SmartStore را بررسی کنید. آن هم به شکل مشابهی ولی پیشرفتهتر از این مقاله، همچین امکانی را دارد.
در ادامه پست پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 5# ، در این پست به تشریح کلاس دایره و بیضی میپردازیم.
ابتدا به تشریح کلاس ترسیم بیضی (Ellipse) میپردازیم.
موفق و موید باشید
در ادامه مطالب قبل:
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 1#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 2#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 3#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 4#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 5#
ابتدا به تشریح کلاس ترسیم بیضی (Ellipse) میپردازیم.
using System.Drawing; namespace PWS.ObjectOrientedPaint.Models { /// <summary> /// Ellipse Draw /// </summary> public class Ellipse : Shape { #region Constructors (2) /// <summary> /// Initializes a new instance of the <see cref="Ellipse" /> class. /// </summary> /// <param name="startPoint">The start point.</param> /// <param name="endPoint">The end point.</param> /// <param name="zIndex">Index of the z.</param> /// <param name="foreColor">Color of the fore.</param> /// <param name="thickness">The thickness.</param> /// <param name="isFill">if set to <c>true</c> [is fill].</param> /// <param name="backgroundColor">Color of the background.</param> public Ellipse(PointF startPoint, PointF endPoint, int zIndex, Color foreColor, byte thickness, bool isFill, Color backgroundColor) : base(startPoint, endPoint, zIndex, foreColor, thickness, isFill, backgroundColor) { ShapeType = ShapeType.Ellipse; } /// <summary> /// Initializes a new instance of the <see cref="Ellipse" /> class. /// </summary> public Ellipse() { ShapeType = ShapeType.Ellipse; } #endregion Constructors #region Methods (1) // Public Methods (1) /// <summary> /// Draws the specified g. /// </summary> /// <param name="g">The g.</param> public override void Draw(Graphics g) { if (IsFill) g.FillEllipse(BackgroundBrush, StartPoint.X, StartPoint.Y, Width, Height); g.DrawEllipse(Pen, StartPoint.X, StartPoint.Y, Width, Height); base.Draw(g); } #endregion Methods } }
این کلاس از شی Shape ارث برده و دارای دو سازنده ساده میباشد که نوع شی ترسیمی را مشخص میکنند، در متد Draw نیز با توجه به توپر یا توخالی بودن شی ترسیم آن انجام میشود، در این کلاس باید متد HasPointInShape بازنویسی (override) شود، در این متد باید تعیین شود که یک نقطه در داخل بیضی قرار گرفته است یا خیر که متاسفانه فرمول بیضی خاطرم نبود. البته به صورت پیش فرض نقطه با توجه به چهارگوشی که بیضی را احاطه میکند سنجیده میشود.
کلاس دایره (Circle) از کلاس بالا (Ellipse) ارث بری دارد که کد آن را در زیر مشاهده مینمایید.
این کلاس شامل دو سازنده میباشد، که در سازنده اول با توجه به نقاط ایتدا و انتهای ترسیم شکل مقدار طول و عرض مستطیل احاطه کننده دایره محاسبه شده و باتوجه به آنها بزرگترین ضلع به عنوان قطر دایره در نظر گرفته میشود و EndPoint شکل مورد نظر تعیین میشود.
در متد HasPointInShape با استفاده از فرمول دایره تعیین میشود که آیا نقطه پارامتر ورودی متد در داخل دایره واقع شده است یا خیر (جهت انتخاب شکل برای جابجایی یا تغییر اندازه).
در پستهای بعد به پیاده سازی اینترفیس نرم افزار خواهیم پرداخت.کلاس دایره (Circle) از کلاس بالا (Ellipse) ارث بری دارد که کد آن را در زیر مشاهده مینمایید.
using System; using System.Drawing; namespace PWS.ObjectOrientedPaint.Models { /// <summary> /// Circle /// </summary> public class Circle : Ellipse { #region Constructors (2) /// <summary> /// Initializes a new instance of the <see cref="Circle" /> class. /// </summary> /// <param name="startPoint">The start point.</param> /// <param name="endPoint">The end point.</param> /// <param name="zIndex">Index of the z.</param> /// <param name="foreColor">Color of the fore.</param> /// <param name="thickness">The thickness.</param> /// <param name="isFill">if set to <c>true</c> [is fill].</param> /// <param name="backgroundColor">Color of the background.</param> public Circle(PointF startPoint, PointF endPoint, int zIndex, Color foreColor, byte thickness, bool isFill, Color backgroundColor) { float x = 0, y = 0; float width = Math.Abs(endPoint.X - startPoint.X); float height = Math.Abs(endPoint.Y - startPoint.Y); if (startPoint.X <= endPoint.X && startPoint.Y <= endPoint.Y) { x = startPoint.X; y = startPoint.Y; } else if (startPoint.X >= endPoint.X && startPoint.Y >= endPoint.Y) { x = endPoint.X; y = endPoint.Y; } else if (startPoint.X >= endPoint.X && startPoint.Y <= endPoint.Y) { x = endPoint.X; y = startPoint.Y; } else if (startPoint.X <= endPoint.X && startPoint.Y >= endPoint.Y) { x = startPoint.X; y = endPoint.Y; } StartPoint = new PointF(x, y); var side = Math.Max(width, height); EndPoint = new PointF(x + side, y + side); ShapeType = ShapeType.Circle; Zindex = zIndex; ForeColor = foreColor; Thickness = thickness; BackgroundColor = backgroundColor; IsFill = isFill; } /// <summary> /// Initializes a new instance of the <see cref="Circle" /> class. /// </summary> public Circle() { ShapeType = ShapeType.Circle; } #endregion Constructors #region Methods (1) // Public Methods (1) /// <summary> /// Points the in sahpe. /// </summary> /// <param name="point">The point.</param> /// <param name="tolerance">The tolerance.</param> /// <returns> /// <c>true</c> if [has point in sahpe] [the specified point]; otherwise, <c>false</c>. /// </returns> public override bool HasPointInSahpe(PointF point, byte tolerance = 5) { float width = Math.Abs(EndPoint.X+tolerance - StartPoint.X-tolerance); float height = Math.Abs(EndPoint.Y+tolerance - StartPoint.Y-tolerance); float diagonal = Math.Max(height, width); float raduis = diagonal / 2; float dx = Math.Abs(point.X - (X + Width / 2)); float dy = Math.Abs(point.Y - (Y + height / 2)); return (dx + dy <= raduis); } #endregion Methods } }
در متد HasPointInShape با استفاده از فرمول دایره تعیین میشود که آیا نقطه پارامتر ورودی متد در داخل دایره واقع شده است یا خیر (جهت انتخاب شکل برای جابجایی یا تغییر اندازه).
موفق و موید باشید
در ادامه مطالب قبل:
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 1#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 2#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 3#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 4#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 5#