public class ModelStateFeatureFilter : IAsyncActionFilter { public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var state = context.ModelState; // store state ... await next(); } }
using System.Collections.Generic; using System.Drawing; using iTextSharp.text; using PdfRpt.Core.Contracts; namespace PdfReportSamples.HexDump { public class GrayTemplate : ITableTemplate { public HorizontalAlignment HeaderHorizontalAlignment { get { return HorizontalAlignment.Center; } } public BaseColor AlternatingRowBackgroundColor { get { return new BaseColor(Color.WhiteSmoke); } } public BaseColor CellBorderColor { get { return new BaseColor(Color.LightGray); } } public IList<BaseColor> HeaderBackgroundColor { get { return new List<BaseColor> { new BaseColor(ColorTranslator.FromHtml("#990000")), new BaseColor(ColorTranslator.FromHtml("#e80000")) }; } } public BaseColor RowBackgroundColor { get { return null; } } public IList<BaseColor> PreviousPageSummaryRowBackgroundColor { get { return new List<BaseColor> { new BaseColor(Color.LightSkyBlue) }; } } public IList<BaseColor> SummaryRowBackgroundColor { get { return new List<BaseColor> { new BaseColor(Color.LightSteelBlue) }; } } public IList<BaseColor> PageSummaryRowBackgroundColor { get { return new List<BaseColor> { new BaseColor(Color.Yellow) }; } } public BaseColor AlternatingRowFontColor { get { return new BaseColor(ColorTranslator.FromHtml("#333333")); } } public BaseColor HeaderFontColor { get { return new BaseColor(Color.White); } } public BaseColor RowFontColor { get { return new BaseColor(ColorTranslator.FromHtml("#333333")); } } public BaseColor PreviousPageSummaryRowFontColor { get { return new BaseColor(Color.Black); } } public BaseColor SummaryRowFontColor { get { return new BaseColor(Color.Black); } } public BaseColor PageSummaryRowFontColor { get { return new BaseColor(Color.Black); } } public bool ShowGridLines { get { return true; } } } }
.MainTableTemplate(template => { template.CustomTemplate(new GrayTemplate()); })
- در کتابخانه iTextSharp، کلاس رنگ توسط BaseColor تعریف شده است. به همین جهت خروجی رنگها را در اینجا نیز بر اساس BaseColor مشاهده میکنید. اگر نیاز داشتید رنگهای تعریف شده در فضای نام استاندارد System.Drawing را به BaseColor تبدیل کنید، فقط کافی است آنرا به سازنده کلاس BaseColor ارسال نمائید.
- اگر علاقمند هستید که معادل رنگهای HTML ایی را در اینجا داشته باشید، میتوان از متد توکار ColorTranslator.FromHtml استفاده کرد.
- برای تعریف رنگی به صورت شفاف (transparent) آنرا مساوی null قرار دهید.
- در اینترفیس فوق، تعدادی از خروجیها به صورت IList است. در این موارد میتوان یک یا دو رنگ را حداکثر معرفی کرد. اگر دو رنگ را معرفی کنید یک گرادیان خودکار از این دو رنگ، تشکیل خواهد شد.
- اگر قالب جدید زیبایی را طراحی کردید، لطفا در این پروژه مشارکت کرده و آنرا به صورت یک وصله ارائه دهید!
تهیه یک منبع داده ناشناس
مثال زیر را در نظر بگیرید. در اینجا قصد داریم معادل Ascii اطلاعات Hex را تهیه کنیم:
using System; using System.Collections; using System.Linq; namespace PdfReportSamples.HexDump { public static class PrintHex { public static char ToSafeAscii(this int b) { if (b >= 32 && b <= 126) { return (char)b; } return '_'; } public static IEnumerable HexDump(this byte[] data) { int bytesPerLine = 16; return data .Select((c, i) => new { Char = c, Chunk = i / bytesPerLine }) .GroupBy(c => c.Chunk) .Select(g => new { Hex = g.Select(c => String.Format("{0:X2} ", c.Char)).Aggregate((s, i) => s + i), Chars = g.Select(c => ToSafeAscii(c.Char).ToString()).Aggregate((s, i) => s + i) }) .Select((s, i) => new { Offset = String.Format("{0:d6}", i * bytesPerLine), Hex = s.Hex, Chars = s.Chars }); } } }
مفهوم فوق از دات نت 3 به بعد تحت عنوان anonymous types در دسترس است. توسط این قابلیت میتوان یک شیء را بدون نیاز به تعریف ابتدایی آن ایجاد کرد. این نوعهای ناشناس توسط واژههای کلیدی new و var تولید میشوند. کامپایلر به صورت خودکار برای هر anonymous type یک کلاس ایجاد میکند.
نکتهای مهم حین کار با کلاسهای ناشناس:
کلاسهای ناشناس به صورت خودکار توسط کامپایلر تولید میشوند و ... از نوع internal هم تعریف خواهند شد. به عبارتی در اسمبلیهای دیگر قابل استفاده نیستند. البته میتوان توسط ویژگی assembly:InternalsVisibleTo ، تعاریف internal یک اسمبلی را دراختیار اسمبلی دیگری نیز گذاشت. ولی درکل باید به این موضوع دقت داشت و اگر قرار است منبع دادهای به این نحو تعریف شود، بهتر است داخل همان اسمبلی تعاریف گزارش باشد.
برای نمایش این نوع اطلاعات حاصل از کوئریهای LINQ میتوان از منبع داده پیش فرض AnonymousTypeList به نحو زیر استفاده کرد:
using System; using System.Text; using PdfRpt.Core.Contracts; using PdfRpt.FluentInterface; namespace PdfReportSamples.HexDump { public class HexDumpPdfReport { public IPdfReportData CreatePdfReport() { return new PdfReport().DocumentPreferences(doc => { doc.RunDirection(PdfRunDirection.LeftToRight); doc.Orientation(PageOrientation.Portrait); doc.PageSize(PdfPageSize.A4); doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" }); }) .DefaultFonts(fonts => { fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\COUR.ttf", Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.TTF"); }) .PagesFooter(footer => { footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy")); }) .PagesHeader(header => { header.DefaultHeader(defaultHeader => { defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png"); defaultHeader.Message("Hex Dump"); }); }) .MainTableTemplate(template => { template.CustomTemplate(new GrayTemplate()); }) .MainTablePreferences(table => { table.ColumnsWidthsType(TableColumnWidthType.Relative); }) .MainTableDataSource(dataSource => { var data = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog."); var list = data.HexDump(); dataSource.AnonymousTypeList(list); }) .MainTableColumns(columns => { columns.AddColumn(column => { column.PropertyName("Offset"); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(0); column.Width(0.5f); column.HeaderCell("Offset"); }); columns.AddColumn(column => { column.PropertyName("Hex"); column.CellsHorizontalAlignment(HorizontalAlignment.Left); column.IsVisible(true); column.Order(1); column.Width(2.5f); column.HeaderCell("Hex"); }); columns.AddColumn(column => { column.PropertyName("Chars"); column.CellsHorizontalAlignment(HorizontalAlignment.Left); column.IsVisible(true); column.Order(2); column.Width(1f); column.HeaderCell("Chars"); }); }) .MainTableEvents(events => { events.DataSourceIsEmpty(message: "There is no data available to display."); }) .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\HexDumpSampleRpt.pdf")); } } }
توضیحات:
در اینجا منبع داده بر اساس کلاسهای کمکی که تعریف کردیم، به نحو زیر مشخص شده است:
.MainTableDataSource(dataSource => { var data = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog."); var list = data.HexDump(); dataSource.AnonymousTypeList(list); })
column.PropertyName("Offset"); //... column.PropertyName("Hex"); //... column.PropertyName("Chars");
نکتهای در مورد خواص تودرتو:
در حین استفاده از AnonymousTypeList امکان تعریف خواص تو در تو نیز وجود دارد. برای مثال فرض کنید که Select نهایی به شکل زیر تعریف شده است و در اینجا OrderInfoData نیز خود یک شیء است:
.Select(x => new { OrderInfo = x.OrderInfoData })
column.PropertyName("OrderInfo.Price");
کتابخانه GMap.Net
سال گذشته بود که به بررسی کتابخانههای موجود برای دات نت که به ساخت نقشههای گوگل (+ ) میپردازند پرداختم. ولی مشکلی که وجود داشت، همه آنها در نهایت یک تصویر jpeg تحویل میدادند. ولی من میخواستم نقشهی من زنده و واکنشگرا باشد تا کاربر بتواند روی آن حرکت کند، زوم کند، مارکرها را جابجا کند و امکانات دیگری که در این نقشه در دسترس است را داشته باشد. برای همین شروع به ساخت یک class library کردم تا کاربر بتواند در محیط سی شارپ، تنظیمات را با اسامی قابل شناخت و یک intellisense قدرتمند بنویسد و در نهایت بر اساس اطلاعات کاربر، این کدها به صورت جاوا اسکریپت تولید شود. میتوانید سورس نهایی کتابخانهی GMap.Net را در گیت هاب، به همراه یک پروژهی نمونه ببینید.
پروژهی وابسته این کتابخانه، MS Ajax Minifier جهت کم حجم کردن کدهای جاوا اسکریپت است. در مورد این کتابخانه در سایت جاری بحث شده است.
برای نصب این کتابخانه میتوانید از طریق دستور زیر در Nuget عمل کنید:
Install-Package GMap.Net
در این کتابخانه مواردی که مورد توجه قرار گرفته است، تنظیمات نقشه و بعد از آن overlayها هستند که شامل مارکرها و اشکال مختلف میباشند. این اشکال شامل رسم مستطیل بر روی نقشه، چند ضلعیها و ... نیز میشوند.
برای شروع نیاز است که یک نمونه از کلاس GoogleMapApi را ایجاد کنید. بعد از آن با استفاده از خصوصیت SetLocation، مختصات مرکز نقشه را تنظیم نمایید. سپس با استفاده از خصوصیات دیگر نیز میتوانید نقشه را تنظیم نمایید. تعدادی از این خصوصیات مثل SetZoomVisibility هستند که با استفاده از آن میتوانید تنظیمات زوم را روی نقشه پیاده سازی کنید. البته فعال کردن این گزینه به تنهایی کافی نیست و باید از طریق خصوصیت ZoomControlOption پیکربندی کنترل زوم را نیز اینجام دهید که این پیکربندی شامل موقعیت قرارگیری کنترلهای زوم و اندازهی دکمهها میباشد و مابقی تنظیمات هم بدین شکل هستند:
به غیر از تنظیمات نقشه، Overlayهای زیر در این کلاس پشتیبانی میشوند:
عنوان | توضیحات |
Marker | یک نشانه گذار که برای مشخص کردن یک محل بر روی نقشه به کار میرود. این علامت گذار شامل خصوصیتهایی چون نقطهی قرارگیری، آیکن، عنوان و انیمیشنی برای نحوهی نمایش آن میباشد. همچنین شامل یک خصوصیت دیگر از نوع InfoWindow است که به شما امکان نمایش یک پنجرهی توضیحات را نیز بر روی مارکر میدهد. این محتوا میتواند به صورت HTML نمایش یابد. |
Circle | در صورتیکه بخواهید ناحیهای دایرهای شکل را بر روی نقشه مشخص کنید، کاربرد دارد. با دادن نقطهی مرکزی و شعاع میتوانید دایره را ترسیم کنید. همچنین شامل خصوصیات ظاهری چون رنگ داخل و حاشیهها و میزان شفافیت نیز میباشد. |
Rectangle | به رسم یک مستطیل میپردازد و تنها لازم است دو مختصات را به آن بدهید و بر اساس این دو نقطه، ناحیهی مستطیلی شکل ترسیم میگردد. در صورتیکه نقاط بیشتری را به آن اضافه کنید، فقط دوتای اولی در نظر گرفته میشوند. این گزینه شامل خصوصیات ظاهری نیز میگردد. |
Polyline | برای ترسیم مسیرها به صورت چند ضلعی به کار میرود و الزامی به بستن مسیرها نیست. دارای خصوصیات ظاهری نیز میباشد. |
polygon | کاملا شبیه Ployline است؛ با این تفاوت که یک چند ضلعی بسته است و میتواند داخل آن با رنگ پر باشد. برای بستن این چند ضلعی لازم نیست تا کاری انجام دهید. خود کلاس، نقطهی اول و آخر را به هم وصل میکند. |
خصوصیات آیتمهای بالا، شامل موارد زیر میشود:
نام خصوصیت | توضیحات |
Id | در سازندهی هر کدام به طور اجباری قرار گرفته است. این id برای زمانی است که بخواهید با استفاده از جاوااسکرپیت با آن ارتباط برقرار کنید. |
Editable | با فعال کردن این خاصیت، به کاربر این اجازه را میدهید که بتواند روی Overlay ویرایش انجام دهد. |
StrokeWeight | ضخامت لبهها را مشخص میکند. |
StrokeColor | رنگ لبه را مشخص میکند. |
StrokeOpacity | میزان شفافیت لبه را بین 0 تا 1 مشخص میکند. |
FillColor | بعضی از المانها مانند چند ضلعیهای بسته و مستطیل که ناحیهی داخلی دارند، شامل این گزینه هستند و رنگ داخل این ناحیه را مشخص میکنند. |
FillOpacity | میزان شفافیت خصوصیت بالا را از 0 تا 1 مشخص میکند. |
Points | با استفاده از این خاصیت میتوانید مختصات را با استفاده از کلاس Location به آن اضافه کنید. برای دایره خصوصیت Point وجود دارد. |
Radius | برای دایره کاربرد دارد. با مقدار نوع Int میتوانید شعاع آن را مقدار دهی کنید. |
public class MiladTower { public GoogleMapApi TestMarker() { var map=new GoogleMapApi(true); var location = new Location(35.7448416, 51.3753212); map.SetLocation(location); map.SetZoom(17); map.SetMapType(MapTypes.ROADMAP); map.SetBackgroundColor(Color.Aqua); map.ZoomControlVisibilty(true); map.ZoomOptions = new zoomControlOptions() { Position = Position.TOP_LEFT, ZoomStyle = ZoomStyle.SMALL }; Marker marker=new Marker("mymarker1"); marker.InfoWindow=new InfoWindow("iw1") { Content = "<b>Milad Tower</b><i>in Tehran</i><br/>Milad Tower is the highest tower in iran,many people and tourists visit it each year, but it's so expensive that i cant afford it as iranian citizen<br/>please see more info at <a href=\"https://en.wikipedia.org/wiki/Milad_Tower\"><img width='16px' height='16px' src='https://en.wikipedia.org/favicon.ico'/>wikipedia</a>" }; marker.MarkerPoint = location; map.Markers.Add(marker); var circle=new CircleMarker("mymarker2"); circle.FillColor = Color.Green; circle.FillOpacity = 0.6f; circle.StrokeColor = Color.Red; circle.StrokeOpacity = 0.8f; circle.Point = location; circle.Radius = 30; circle.Editable = true; circle.StrokeWeight = 3; map.Circles.Add(circle); Rectangle rect=new Rectangle("rect1"); rect.FillColor = Color.Black; rect.FillOpacity = 0.4f; rect.Points.Add(new Location(35.74728723483808, 51.37550354003906)); rect.Points.Add(new Location(35.74668641224311, 51.376715898513794)); map.Rectangles.Add(rect); Polyline polyline=new Polyline("poly1"); polyline.Points.Add(new Location(35.74457043569041, 51.373915672302246)); polyline.Points.Add(new Location(35.74470976097927, 51.37359380722046)); polyline.Points.Add(new Location(35.744378863020074, 51.37337923049927)); polyline.StrokeColor = Color.Blue; polyline.StrokeWeight = 2; map.Polylines.Add(polyline); Polygon polygon=new Polygon("poly2"); polygon.Points.Add(new Location(35.746494844665094, 51.374655961990356)); polygon.Points.Add(new Location(35.74635552250061, 51.37283205986023)); polygon.Points.Add(new Location(35.74598109297522, 51.372681856155396)); polygon.Points.Add(new Location(35.7454934611854, 51.37361526489258)); polygon.FillColor = Color.Black; polygon.FillOpacity = 0.5f; polygon.StrokeColor = Color.Gray; polygon.StrokeWeight = 1; map.Polygons.Add(polygon); return map; } }
@section javascript { @{ var map = new MiladTower().TestMarker(); @map.ShowMapForMvc("mapdiv") } } <br/><br/> <div id="mapdiv" style="width:600px;height:600px;"></div>
در نهایت نقشهی زیر نمایش داده میشود:
کم حجم کردن کدها
در صورتیکه به سورس صفحه نگاهی بیندازید، میبینید که کدهای جاوا اسکریپت، داخل صفحه نوشته شدهاند. اگر بخواهید برای کم حجمتر شدن کد، عملیات minify را انجام دهید، با true شدن خصوصیت minified با استفاده از کتابخانهی وابستهاش (MS Ajax Minifier) اینکار را انجام میدهد.
انتقال کدها به یک فایل خارجی
بسیاری از ما برای نوشتن کدهای جاوا اسکریپت، از یک فایل خارجی استفاده میکنیم. برای داشتن این قابلیت میتوانید به جای ShowMapForMVC متد CallJs را صدا بزنید تا کتابخانه api گوگل را صدا بزند و سپس در یک اکشن متد، متد GiveJustJS را صدا بزنید و طبق مقالهی موجود در سایت جاری محتوای آن را برگردانید و به عنوان یک فایل JS به این اکشن متد لینک بدهید. کدهای زیر به شما نحوهی این کار را نشان میدهند:
ابتدا در یک اکشن متد، کد زیر را وارد میکنیم:
public ActionResult MiladJs() { var output = new MiladTower().TestMarker().GiveJustJs("mapdiv"); Response.ContentType = "text/javascript"; return Content(output); }
بعد از آن در ویووی مربوطه کد زیر را داریم:
@section javascript { @{ var map = new MiladTower().TestMarker(); @map.CallJs() <script type="text/javascript" src="@Url.Action("MiladJs","Home")"></script> } } <br/><br/> <div id="mapdiv" style="width:600px;height:600px;"></div>
نحوهی کارکرد این کتابخانه
برای آشنایی با نحوهی کارکرد آن باید بدانید که اساس کار این کتابخانه string interpolation است. این کتابخانه کلاسی را به صورت Partial دارد که بین چندین فایل تقسیم شده است و هر یک از فایلها، با نام محتوای آن نامگذاری شدهاند. Public methods متدهای عمومی، private methods متدهای خصوصی، Constants یا ثابتها که حاوی تمام دستورات جاوا اسکریپتی هستند و در نهایت خود کلاس اصلی GoogleMapApi که حاوی کدهای اجرایی و تشکیل کد جاوا اسکریپت میباشد. در کنار کلاس اصلی، کلاسهای Overlay هم قرار دارند که شامل اطلاعات اشیاء روی نقشهها هستند؛ مثل مارکرها و چندضلعیها و ... و در نهایت یک سری کلاس و نوع شمارشی Enum شامل خصوصیتهایی که برای تنظیمات و پیکربندی نقشه به کار میروند.
کلاس GoogleMapApi برای ایجاد کدها، دادههایی را که برای هر کلاس در نظر گرفتهاید، با استفاده از interpolation و ثابتهای حاوی کد جاوا اسکریپت، در یک رشتهی جدید قرار میدهند و این رشتهها با اتصال درست در موقعیت خود، کد نهایی جاوا اسکریپت را تولید میکنند.
Roslyn #5
همانطور که از قسمت قبل بهخاطر دارید، برای دسترسی به اطلاعات semantics، نیاز به یک context مناسب که همان Compilation API است، میباشد. این context دارای اطلاعاتی مانند دسترسی به تمام نوعهای تعریف شدهی توسط کاربر و متادیتاهای ارجاعی، مانند کلاسهای پایهی دات نت فریمورک است. بنابراین پس از ایجاد وهلهای از Compilation API، کار با فراخوانی متد GetSemanticModel آن ادامه مییابد. در ادامه با مثالهایی، کاربرد این متد را بررسی خواهیم کرد.
ساختار جدید Optional
خروجیهای تعدادی از متدهای Roslyn با ساختار جدیدی به نام Optional ارائه میشوند:
public struct Optional<T> { public bool HasValue { get; } public T Value { get; } }
دریافت مقادیر ثابت Literals
فرض کنید میخواهیم مقدار ثابت ; int x = 42 را دریافت کنیم. برای اینکار ابتدا باید syntax tree آن تشکیل شود و سپس نیاز به یک سری حلقه و if و else و همچنین بررسی نال بودن بسیاری از موارد است تا به نود مقدار ثابت 42 برسیم. سپس متد GetConstantValue مربوط به GetSemanticModel را بر روی آن فراخوانی میکنیم تا به مقدار واقعی آن که ممکن است در اثر محاسبات جاری تغییر کرده باشد، برسیم.
اما روش بهتر و توصیه شده، استفاده از CSharpSyntaxWalker است که در انتهای قسمت سوم معرفی شد:
class ConsoleWriteLineWalker : CSharpSyntaxWalker { public ConsoleWriteLineWalker() { Arguments = new List<ExpressionSyntax>(); } public List<ExpressionSyntax> Arguments { get; } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { var member = node.Expression as MemberAccessExpressionSyntax; var type = member?.Expression as IdentifierNameSyntax; if (type != null && type.Identifier.Text == "Console" && member.Name.Identifier.Text == "WriteLine") { if (node.ArgumentList.Arguments.Count == 1) { var arg = node.ArgumentList.Arguments.Single().Expression; Arguments.Add(arg); return; } } base.VisitInvocationExpression(node); } }
در ادامه نحوهی استفادهی از این SyntaxWalker را ملاحظه میکنید. در اینجا ابتدا سورس کدی حاوی یک سری Console.WriteLine که دارای تک آرگومانهای ثابتی هستند، تبدیل به syntax tree میشود. سپس از روی آن CSharpCompilation تولید میگردد تا بتوان به اطلاعات semantics دسترسی یافت:
static void getConstantValue() { // Get the syntax tree. var code = @" using System; class Foo { void Bar(int x) { Console.WriteLine(3.14); Console.WriteLine(""qux""); Console.WriteLine('c'); Console.WriteLine(null); Console.WriteLine(x * 2 + 1); } } "; var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); // Get the semantic model from the compilation. var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib); var model = comp.GetSemanticModel(tree); // Traverse the tree. var walker = new ConsoleWriteLineWalker(); walker.Visit(root); // Analyze the constant argument (if any). foreach (var arg in walker.Arguments) { var val = model.GetConstantValue(arg); if (val.HasValue) { Console.WriteLine(arg + " has constant value " + (val.Value ?? "null") + " of type " + (val.Value?.GetType() ?? typeof(object))); } else { Console.WriteLine(arg + " has no constant value"); } } }
خروجی نمایش داده شدهی توسط برنامه به صورت ذیل است:
3.14 has constant value 3.14 of type System.Double "qux" has constant value qux of type System.String 'c' has constant value c of type System.Char null has constant value null of type System.Object x * 2 + 1 has no constant value
درک مفهوم Symbols
اینترفیس ISymbol در Roslyn، ریشهی تمام Symbolهای مختلف مدل سازی شدهی در آن است که تعدادی از آنها را در تصویر ذیل مشاهده میکنید:
API کار با Symbols بسیار شبیه به API کار با Reflection است با این تفاوت که در زمان آنالیز کدها رخ میدهد و نه در زمان اجرای برنامه. همچنین در Symbols API امکان دسترسی به اطلاعاتی مانند locals, labels و امثال آن نیز وجود دارد که با استفاده از Reflection زمان اجرای برنامه قابل دسترسی نیستند. برای مثال فضاهای نام در Reflection صرفا به صورت رشتهای، با دات جدا شده از نوعهای آنالیز شدهی توسط آن است؛ اما در اینجا مطابق تصویر فوق، یک اینترفیس مجزای خاص خود را دارد. جهت سهولت کار کردن با Symbols، الگوی Visitor با معرفی کلاس پایهی SymbolVisitor نیز پیش بینی شدهاست.
static void workingWithSymbols() { // Get the syntax tree. var code = @" using System; class Foo { void Bar(int x) { // #insideBar } } class Qux { protected int Baz { get; set; } } "; var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); // Get the semantic model from the compilation. var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib); var model = comp.GetSemanticModel(tree); // Traverse enclosing symbol hierarchy. var cursor = code.IndexOf("#insideBar"); var barSymbol = model.GetEnclosingSymbol(cursor); for (var symbol = barSymbol; symbol != null; symbol = symbol.ContainingSymbol) { Console.WriteLine(symbol); } // Analyze accessibility of Baz inside Bar. var bazProp = ((CompilationUnitSyntax)root) .Members.OfType<ClassDeclarationSyntax>() .Single(m => m.Identifier.Text == "Qux") .Members.OfType<PropertyDeclarationSyntax>() .Single(); var bazSymbol = model.GetDeclaredSymbol(bazProp); var canAccess = model.IsAccessible(cursor, bazSymbol); }
Foo.Bar(int) Foo <global namespace> Demo.exe Demo, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
همچنین در ادامهی کد، توسط متد IsAccessible قصد داریم بررسی کنیم آیا Symbol قرار گرفته در محل کرسر، دسترسی به خاصیت protected کلاس Qux را دارد یا خیر؟ که پاسخ آن خیر است.
آشنایی با Binding symbols
یکی از مراحل کامپایل کد، binding نام دارد و در این مرحله است که اطلاعات Symbolic هر نود از Syntax tree دریافت میشود. برای مثال در اینجا مشخص میشود که این x، آیا یک متغیر محلی است، یا یک فیلد و یا یک خاصیت؟
مثال ذیل بسیار شبیه است به مثال getConstantValue ابتدای بحث، با این تفاوت که در حلقهی آخر کار از متد GetSymbolInfo استفاده شدهاست:
static void bindingSymbols() { // Get the syntax tree. var code = @" using System; class Foo { private int y; void Bar(int x) { Console.WriteLine(x); Console.WriteLine(y); int z = 42; Console.WriteLine(z); Console.WriteLine(a); } }"; var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); // Get the semantic model from the compilation. var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib); var model = comp.GetSemanticModel(tree); // Traverse the tree. var walker = new ConsoleWriteLineWalker(); walker.Visit(root); // Bind the arguments. foreach (var arg in walker.Arguments) { var symbol = model.GetSymbolInfo(arg); if (symbol.Symbol != null) { Console.WriteLine(arg + " is bound to " + symbol.Symbol + " of type " + symbol.Symbol.Kind); } else { Console.WriteLine(arg + " could not be bound"); } } }
x is bound to int of type Parameter y is bound to Foo.y of type Field z is bound to z of type Local a could not be bound
فرض کنید تیم برنامهنویس متلب و تیم برنامهنویس دات نت در تعامل با یکدیگر هستند. وظیفه تیم برنامه نویسی متلب به شرح زیر میباشد :
1- نوشتن توابع در متلب و تست کردن آنها جهت توسعه و ارائه مناسب به تیم مقابل
2- درست کردن کامپوننت دات نت در متلب با استفاده از محیط Deployment Tool GUI (با اجرای دستور deploytool در متلب)
3- استفاده از یک پکیج بستهبندی شده از فایلهای قابل ارائه به تیم مقابل (اختیاری)
4- کپی پکیج در محل از قبل تعیین شده توسط دو تیم یا ارائه آن به تیم مقابل جهت استفاده
برای مثال M فایل (اصطلاح فایلها در متلب همانند کلاس در دات نت) makesquare.m را که در مسیر
matlabroot\toolbox\dotnetbuilder\Examples\VS8\NET\MagicSquareExample\MagicSquareComp
function y = makesquare(x) %MAKESQUARE Magic square of size x. % Y = MAKESQUARE(X) returns a magic square of size x. % This file is used as an example for the MATLAB % Builder NE product. % Copyright 2001-2012 The MathWorks, Inc. y = magic(x);
در صورتی که x برابر 5 انتخاب شود خروجی متلب بصورت زیر خواهد بود :
17 24 1 8 15 23 5 7 14 16 4 6 13 20 22 10 12 19 21 3 11 18 25 2 9
makeSqr | Project Name |
MLTestClass | Class Name |
makesquare.m | File to compile |
نام و مسیر پروژه را تعیین کنید سپس از منوی کشویی نوع پروژه، که دات نت اسمبلی باشد را انتخاب کنید. پنجرهای در به شکل زیر مشاهده خواهد شد :
در تب build اگر قصد استفاده از اپلیکیشن COM را دارید و یا فایلهایی جهت تکمیل پروژه قصد پیوست دارید را در قسمت پایین Add files را انتخاب کنید. و اگر قصد استفاده از اپلیکیشن دات نت را دارید قسمت بالایی Add classes را انتخاب کنید و نام کلاس را وارد کنید.
سپس برای کلاس مورد نظر فایلهای متلبی که قصد کامپایل کردن آنها را دارید از قسمت Add files پیوست کنید. در صورتیکه قصد اضافه کردن کلاس اضافی را داشتید مجددا مراحل را طی کنید. در انتها دکمه build را زده تا عملیات کامپایل آغاز شود. اما برای استفاده تیم برنامهنویسی دات نت احتیاج به کامپایلر متلب میباشد که این مهم در پکیجی که به این تیم ارائه خواهد شد مد نظر قرار خواهد گرفت.در قسمت تب Package گزینه Add MCR را انتخاب نمائید :
بعد از انتخاب، دو گزینه برای انتخاب وجود دارد که بطور خلاصه گزینه اول فایلهای کامپایلر متلب در داخل پروژه جهت ارائه قرار میگیرد. همچنین این گزینه جهت استفاده در مواقع درون شبکهای، مواردی که فضای دیسک و عملکرد و .... چندان اهمیت ندارد مورد استفاده قرار میگیرد. اما گزینه دوم عکس قضیه بالا عمل میکند و برای تعداد یوزر بالا و شبکهای و ... مورد استفاده میباشد.
در اینجا گزینه اول را انتخاب میکنیم. در صورتیکه فایلهای دیگری جهت ضمیمه به پکیج احتیاج است به آن اضافه میکینم.
سپس کلید پکیج را زده تا پکیج مورد نظر آماده شود. دقت داشته باشید که بعد از انتخاب کامپایلر متلب، حجم پکیج نزدیک به 400 مگابایت خواهد شد. پکیج مورد نظر بصورت یک فایل exe فشرده خواهد شد.
معمولا پکیج شامل فایلهای زیر باید باشد :
Documentation files | componentName.xml |
Program Database File, which contains debugging information | componentName.pdb (if Debug optionis selected) |
Component assembly file | componentName.dll |
MCR Installer (if not already installed on the target machine). | MCR Installer |
بعد از طی مراحل فوق نوبت به تیم برنامهنویسی دات نت میرسد. بعد از دریافت پکیج از تیم برنامهنویسی متلب در صورتیکه بر روی سیستم هدف کامپایلر متلب و یا خود متلب نصب نیست باید از داخل پکیج این کامپایلر نصب شود.
دقت داشته باشید که ورژن کامپایلر بر روی سیستم باید با ورژن پکیج دریافتی یکی باشد.
در VS یک پروژه کنسول ایجاد کنید و از فولدر پکیج پروژه دریافتی در زیرفولدر distrib فایل makeSqr.dll را به رفرنس برنامه VS اضافه کنید.
در ادامه از مسیر نصب کامپایلر فایل MWArray.dll را هم به رفرنس پروژه اضافه کنید. این فایل جهت تبادل داده اپلیکیشن با کامپایلر متلب مورد استفاده قرار میگیرد.
installation_folder\toolbox\dotnetbuilder\bin\architecture\framework_version
using System; using MathWorks.MATLAB.NET.Arrays; using MyComponentName;
static void Main(string[] args) { MLTestClass obj = new MLTestClass(); MWArray[] result = obj.makesquare(1, 5); MWNumericArray output = (MWNumericArray)result[0]; Console.WriteLine(output); }
1- MWNumericArray یک اینترفیس جهت تعیین و نمایش نوع آرایههای عددی در متلب است.
2- MWArray یک کلاس abstract جهت دسترسی، فرمتدهی و مدیریت آرایههای متلب میباشد.
3- عدد 1 مشخص کننده تعداد خروجی تابع متلب و عدد 5 ورودی تابع میباشد.
نکته:
ورژن فریمورک دات نت در هنگام کامپایل با ورژن Mwarray.dll باید یکی باشد.
// api.model.ts export interface Customer { id: number; name: string; } export interface User { id: number; isActive: boolean; }
// using the interfaces import { Customer, User } from './api.model'; export class MyComponent { cust: Customer; }
// api.model.ts namespace ApiModel { export interface Customer { id: number; name: string; } export interface User { id: number; isActive: boolean; } }
// using the interfaces export class MyComponent { cust: ApiModel.Customer; }
// api.v2.model.ts namespace ApiModel { export interface Order { id: number; total: number; } }
export class MyComponent { cust: ApiModel.Customer; order: ApiModel.Order; }
// api.model.d.ts interface Customer { id: number; name: string; }
// using the interfaces of d file export class MyComponent { cust: Customer; }
// lib.es5.d.ts type Partial<T> = { [P in keyof T]?: T[P]; };
import { Customer } from './api.model'; export class MyComponent { cust: Partial<Customer>; / ngOninit() { this.cust = { name: 'jane' }; } }
if (false) { console.log('x'); }
if (false) { // @ts-ignore console.log('x'); }
استفاده از JSON.NET در ASP.NET MVC
JsonNetValueProviderFactory.cs
+ نحوهی ثبت بهتر این کلاس دقیقا در همان ایندکس اصلی آن:
public static void RegisterFactory() { var defaultJsonFactory = ValueProviderFactories.Factories .OfType<JsonValueProviderFactory>().FirstOrDefault(); var index = ValueProviderFactories.Factories.IndexOf(defaultJsonFactory); ValueProviderFactories.Factories.Remove(defaultJsonFactory); ValueProviderFactories.Factories.Insert(index, new JsonNetValueProviderFactory()); }
- قابلیت ترسیم اشیا روی بوم گرافیکی دلخواه
- قابلیت جابجایی اشیا
- قابلیت تغییر رنگ اشیا
- ترسیم اشیا توپر و تو خالی
- تعیین پهنای خط شی ترسیم شده
- تعیین رنگ پس زمینه در صورت تو پر بودن شی
- قابلیت پیش نمایش رسم شکل در زمان ترسیم اشیا
- توانایی انتخاب اشیا
- تعیین عمق شی روی بوم گرافیکی مورد نظر
- ترسیم اشیایی مانند خط، دایره، بیضی، مربع، مستطیل، لوزی، مثلث
- قابلیت تغییر اندازه اشیا ترسیم شده
خوب برای شروع ابتدا یک پروژه از نوع Windows Application ایجاد میکنیم (البته برای این قسمت میتوانیم یک پروژه Class Library ایجاد کنیم)
سپس یک پوشه به نام Models به پروزه اضافه نمایید.
خوب در این پروژه یک کلاس پایه به نام Shape در نظر میگیریم.
همه اشیا ما دارای نقطه شروع، نقطه پایان، رنگ قلم، حالت انتخاب، رنگ پس زمینه، نوع شی، .... میباشند که بعضی از خصوصیات را توسط خصوصیات دیگر محاسبه میکنیم. مثلا خاصیت Width و Height و X و Y توسط خصوصیات نقطه شروع و پایان میتوانند محاسبه شوند.
ساختار کلاسهای پروزه ما به صورت زیر است که مرحله به مرحله کلاسها پیاده سازی خواهند شد.
با توجه به تصویر بالا (البته این تجزیه تحلیل شخصی من بوده و دوستان به سلیقه خود ممکن است این ساختار را تغییر دهند)
نوع شمارشی ShapeType: در این نوع شمارشی انواع شیهای موجود در پروژه معرفی شده است
محتوای این نوع به صورت زیر تعریف شده است:
namespace PWS.ObjectOrientedPaint.Models { /// <summary> /// Shape Type in Paint /// </summary> public enum ShapeType { /// <summary> /// هیچ /// </summary> None = 0, /// <summary> /// خط /// </summary> Line = 1, /// <summary> /// مربع /// </summary> Square = 2, /// <summary> /// مستطیل /// </summary> Rectangle = 3, /// <summary> /// بیضی /// </summary> Ellipse = 4, /// <summary> /// دایره /// </summary> Circle = 5, /// <summary> /// لوزی /// </summary> Diamond = 6, /// <summary> /// مثلث /// </summary> Triangle = 7, } }
Gulp #5
در مقالات قبلی به طور کامل با گالپ آشنا شدیم و گفتیم که میتواند ما را در بهینه سازی ورک فلویمان کمک کند. در این قسمت یاد خواهیم گرفت که چگونه تجربهی کاربری بهتری را از سرعت بارگذاری سایتمان ایجاد کنیم.
افزایش کارآیی Performance وب با گالپ
۱− کنار هم قرار دادن و فشرده کردن فایلهای جاوا اسکریپت
npm install --save-dev gulp-uglify gulp-concat
// first load all required js files // concat them in to script.min.js // and minify it. gulp.task('js', function() { return gulp.src([ config.bowerDir + '/jquery/dist/jquery.min.js', // این فایل وابستگی فایلهای زیر است config.bowerDir + '/materialize/dist/js/materialize.min.js', './resources/js/app.js' ]) .pipe(concat('script.min.js')) .pipe(uglify()) .pipe(size()) .pipe(gulp.dest('./public/js')); });
فشرده کردن جاوا اسکریپت، حجم فایلها را ۳۰ تا ۹۰ درصد کاهش میدهد.
۲− حذف سکلتورهای بدون استفاده css
npm install gulp-uncss --save-dev
gulp.task('css', function() { return sass(config.sassPath + '/style.scss', { style: 'compressed', loadPath: [ './resources/sass', config.bowerDir + '/materialize/sass' ] }) .on('error', util.log) .pipe(size()) .pipe(uncss({ html: ['./index.html', './posts.html'] })) .pipe(gulp.dest('./public/css')) .pipe(size()) .pipe(connect.reload()); });
gulp.task('css', function() { return sass(config.sassPath + '/style.scss', { style: 'compressed', loadPath: [ './resources/sass', config.bowerDir + '/materialize/sass' ] }) .on('error', util.log) .pipe(size()) .pipe(uncss({ html: ['./index.html', './posts.html'], timeout : 2000, // wait for load js files ignore: [ ".waves-ripple ", ".drag-target", "#sidenav-overlay", ".waves-effect", ".waves-effect .waves-ripple", ".waves-effect.waves-pinck .waves-ripple", ".waves-block.waves-light" ] })) .pipe(minifyCss()) .pipe(size()) .pipe(gulp.dest('./public/css')) .pipe(connect.reload()); });
در این قسمت قصد داریم اطلاعات بازگشتی از لایه سرویس برنامه را کش کنیم؛ اما نمیخواهیم مدام کدهای مرتبط با کش کردن اطلاعات را در مکانهای مختلف لایه سرویس پراکنده کنیم. میخواهیم یک ویژگی یا Attribute سفارشی را تهیه کرده (مثلا به نام CacheMethod) و به متد یا متدهایی خاص اعمال کنیم. سپس برنامه، در زمان اجرا، بر اساس این ویژگیها، خروجیهای متدهای تزئین شده با ویژگی CacheMethod را کش کند.
در اینجا نیز از ترکیب StructureMap و DynamicProxy پروژه Castle، برای رسیدن به این مقصود استفاده خواهیم کرد. به کمک StructureMap میتوان در زمان وهله سازی کلاسها، آنها را به کمک متدی به نام EnrichWith توسط یک محصور کننده دلخواه، مزین یا غنی سازی کرد. این مزین کننده را جهت دخالت در فراخوانیهای متدها، یک DynamicProxy درنظر میگیریم. با پیاده سازی اینترفیس IInterceptor کتابخانه DynamicProxy مورد استفاده و تحت کنترل قرار دادن نحوه و زمان فراخوانی متدهای لایه سرویس، یکی از کارهایی را که میتوان انجام داد، کش کردن نتایج است که در ادامه به جزئیات آن خواهیم پرداخت.
پیشنیازها
ابتدا یک برنامه جدید کنسول را آغاز کنید. تنظیمات آنرا از حالت Client profile به Full تغییر دهید.
سپس همانند قسمتهای قبل، ارجاعات لازم را به StructureMap و Castle.Core نیز اضافه نمائید:
PM> Install-Package structuremap PM> Install-Package Castle.Core
از این جهت که از HttpRuntime.Cache قصد داریم استفاده کنیم. HttpRuntime.Cache در برنامههای کنسول نیز کار میکند. در این حالت از حافظه سیستم استفاده خواهد کرد و در پروژههای وب از کش IIS بهره میبرد.
ویژگی CacheMethod مورد استفاده
using System; namespace AOP02.Core { [AttributeUsage(AttributeTargets.Method)] public class CacheMethodAttribute : Attribute { public CacheMethodAttribute() { // مقدار پیش فرض SecondsToCache = 10; } public double SecondsToCache { get; set; } } }
در ویژگی CacheMethod، خاصیت SecondsToCache بیانگر مدت زمان کش شدن نتیجه متد خواهد بود.
ساختار لایه سرویس برنامه
using System; using System.Threading; using AOP02.Core; namespace AOP02.Services { public interface IMyService { string GetLongRunningResult(string input); } public class MyService : IMyService { [CacheMethod(SecondsToCache = 60)] public string GetLongRunningResult(string input) { Thread.Sleep(5000); // simulate a long running process return string.Format("Result of '{0}' returned at {1}", input, DateTime.Now); } } }
تدارک یک CacheInterceptor
using System; using System.Web; using Castle.DynamicProxy; namespace AOP02.Core { public class CacheInterceptor : IInterceptor { private static object lockObject = new object(); public void Intercept(IInvocation invocation) { cacheMethod(invocation); } private static void cacheMethod(IInvocation invocation) { var cacheMethodAttribute = getCacheMethodAttribute(invocation); if (cacheMethodAttribute == null) { // متد جاری توسط ویژگی کش شدن مزین نشده است // بنابراین آنرا اجرا کرده و کار را خاتمه میدهیم invocation.Proceed(); return; } // دراینجا مدت زمان کش شدن متد از ویژگی کش دریافت میشود var cacheDuration = ((CacheMethodAttribute)cacheMethodAttribute).SecondsToCache; // برای ذخیره سازی اطلاعات در کش نیاز است یک کلید منحصربفرد را // بر اساس نام متد و پارامترهای ارسالی به آن تهیه کنیم var cacheKey = getCacheKey(invocation); var cache = HttpRuntime.Cache; var cachedResult = cache.Get(cacheKey); if (cachedResult != null) { // اگر نتیجه بر اساس کلید تشکیل شده در کش موجود بود // همان را بازگشت میدهیم invocation.ReturnValue = cachedResult; } else { lock (lockObject) { // در غیر اینصورت ابتدا متد را اجرا کرده invocation.Proceed(); if (invocation.ReturnValue == null) return; // سپس نتیجه آنرا کش میکنیم cache.Insert(key: cacheKey, value: invocation.ReturnValue, dependencies: null, absoluteExpiration: DateTime.Now.AddSeconds(cacheDuration), slidingExpiration: TimeSpan.Zero); } } } private static Attribute getCacheMethodAttribute(IInvocation invocation) { var methodInfo = invocation.MethodInvocationTarget; if (methodInfo == null) { methodInfo = invocation.Method; } return Attribute.GetCustomAttribute(methodInfo, typeof(CacheMethodAttribute), true); } private static string getCacheKey(IInvocation invocation) { var cacheKey = invocation.Method.Name; foreach (var argument in invocation.Arguments) { cacheKey += ":" + argument; } // todo: بهتر است هش این کلید طولانی بازگشت داده شود // کار کردن با هش سریعتر خواهد بود return cacheKey; } } }
توضیحات ریز قسمتهای مختلف آن به صورت کامنت، جهت درک بهتر عملیات، ذکر شدهاند.
اتصال Interceptor به سیستم
خوب! تا اینجای کار صرفا تعاریف اولیه تدارک دیده شدهاند. در ادامه نیاز است تا DI و DynamicProxy را از وجود آنها مطلع کنیم.
using System; using AOP02.Core; using AOP02.Services; using Castle.DynamicProxy; using StructureMap; namespace AOP02 { class Program { static void Main(string[] args) { ObjectFactory.Initialize(x => { var dynamicProxy = new ProxyGenerator(); x.For<IMyService>() .EnrichAllWith(myTypeInterface => dynamicProxy.CreateInterfaceProxyWithTarget(myTypeInterface, new CacheInterceptor())) .Use<MyService>(); }); var myService = ObjectFactory.GetInstance<IMyService>(); Console.WriteLine(myService.GetLongRunningResult("Test")); Console.WriteLine(myService.GetLongRunningResult("Test")); } } }
حال اگر برنامه را اجرا کنید یک چنین خروجی قابل مشاهده خواهد بود:
Result of 'Test' returned at 2013/04/09 07:19:43 Result of 'Test' returned at 2013/04/09 07:19:43
از این پیاده سازی میشود به عنوان کش سطح دوم ORMها نیز استفاده کرد (صرفنظر از نوع ORM در حال استفاده).
دریافت مثال کامل این قسمت
AOP02.zip