public interface IFoo { IPerson Person { get; } ICompany Company { get; } DateTime Date { get; } long NormalShareCount { get; } } public interface IPerson { string Name{get;} } public interface ICompany { string Name{get;} }
ساختار پروژه های Angular
$routeProvider.when('/about', {templateUrl:'views/about.html', resolve:{deps:function($q, $rootScope) { var deferred = $q.defer(); var dependencies = [ 'controllers/AboutViewController.js', 'directives/some-directive.js' ]; //*نکته اول $script(dependencies, function() { // *نکته دوم $rootScope.$apply(function() { deferred.resolve(); }); }); return deferred.promise; }}})
در ابتدای بحث، برای آشنایی بیشتر با HTML Helperها به مطالعه این مقاله بپردازین.
در این مقاله قرار است برای یک HTML Helper خاص، قالب نمایشی اختصاصی خودمان را طراحی کنیم و به نحوی HTML Helper موجود را سفارشی سازی کنیم. به عنوان مثال میخواهیم خروجی یک EditorFor() برای یک نوع خاص، به حالت دلخواهی باشد که ما خودمان آن را تولیدش کردیم؛ یا اصلا نه. حتی میشود برای خروجی یک EditorFor() که خصوصیتی از جنس string را میخواهیم به آن انتساب دهیم، به جای تولید input، یک مقدار متنی را برگردانیم. به این حالت:
<div> @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" }) <div> @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" }) </div> </div> <div> @Html.LabelFor(model => model.Genre, htmlAttributes: new { @class = "control-label col-md-2" }) <div> @Html.EditorFor(model => model.Genre, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Genre, "", new { @class = "text-danger" }) </div> </div>
در ادامه یک پروژهی عملی را شروع کرده و در آن کاری را که میخواهیم، انجام میدهیم. پروژهی ما به این شکل میباشد که قرار است در آن به ثبت کتاب بپردازیم و برای هر کتاب هم یک سبک داریم و قسمت سبک کتابهای ما یک Enum است که از قبل میخواهیم مقدارهایش را تعریف کنیم.
مدل برنامه
public class Books { public int Id { get; set; } [Required] [StringLength(255)] public string Name { get; set; } public Genre Genre { get; set; } }
public enum Genre { [Display(Name = "Non Fiction")] NonFiction, Romance, Action, [Display(Name = "Science Fiction")] ScienceFiction }
در داخل کلاس Books یک خصوصیت از جنس Genre برای سبک کتابها داریم و در داخل نوع شمارشی Genre، سبکهای ما تعریف شدهاند. همچنین هر کدام از سبکها هم به ویژگی Display مزین شدهاند تا بتونیم بعدا از مقدار آنها استفاده کنیم.
کنترلر برنامه
public class BookController : Controller { // GET: Book public ActionResult Index() { return View(DataAccess.DataContext.Book.ToList()); } public ActionResult Create() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(Books model) { if (!ModelState.IsValid) return View(model); try { DataAccess.DataContext.Book.Add(model); DataAccess.DataContext.SaveChanges(); return RedirectToAction("Index"); } catch (Exception ex) { ModelState.AddModelError("", ex.Message); return View(model); } } public ActionResult Edit(int id) { try { var book = DataAccess.DataContext.Book.Find(id); return View(book); } catch (Exception ex) { return View("Error"); } } [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit(Books model) { if (!ModelState.IsValid) return View(model); try { DataAccess.DataContext.Book.AddOrUpdate(model); DataAccess.DataContext.SaveChanges(); return RedirectToAction("Index"); } catch (Exception ex) { ModelState.AddModelError("", ex.Message); return View(model); } } public ActionResult Details(int id) { try { var book = DataAccess.DataContext.Book.Find(id); return View(book); } catch (Exception ex) { return View("Error"); } } }
در قسمت کنترلر هم کار خاصی جز عملیات اصلی نوشته نشدهاست. لیست کتابها را از پایگاه داده بیرون آوردیم و از طریق اکشن Index به نمایش گذاشتیم. با اکشنهای Create، Edit و Details هم کارهای روتین مربوط به خودشان را انجام دادیم. نکتهی قابل تذکر، DataAccess میباشد که کلاسی است که با آن ارتباط برقرار شده با EF و سپس اطلاعات واکشی و تزریق میشوند.
View مربوط به اکشن Create برنامه
@using Book.Entities @model Book.Entities.Books @{ ViewBag.Title = "Create"; } <h2>New Book</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div> <h4>Books</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div> @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" }) <div> @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" }) </div> </div> <div> @Html.LabelFor(model => model.Genre, htmlAttributes: new { @class = "control-label col-md-2" }) <div> @Html.EditorFor(model => model.Genre, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Genre, "", new { @class = "text-danger" }) </div> </div> <div> <div> <input type="submit" value="Create" /> <input type="reset" value="Reset" /> @Html.ActionLink("Back to List", "Index", null, new {@class="btn btn-default"}) </div> </div> </div> } @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
View برنامه هم همان ویویی است که خود Visual Studio برای ما ساختهاست. به جز یک سری دستکاریهایی داخل سیاساس، هدف از گذاشتن View مربوط به Create این بود که قرار است بر روی این قسمت کار کنیم. اگر پروژه رو اجرا کنید و به قسمت Create بروید، مشاهده خواهید کرد که برای Genre یک input ساخته شدهاست که کاربر باید در آن مقدار وارد کند. ولی اگر یادتان باشد، ما سبکهای نگارشی خودمان را در نوع شمارشی Genre ایجاد کرده بودیم. پس عملا باید یک لیست به کاربر نشان داده شود که تا از آن لیست، نوع را انتخاب کند. میتوانیم بیایم همینجا در داخل View مربوطه، بهجای استفاده از HTML Helper پیشفرض، از DropDownList یا EnumFor استفاده کنیم و به طریقی این لیست را ایجاد کنیم. ولی چون قرار است در این مثال به شرح موضوع مقاله خودمان بپردازیم، این کار را انجام نمیدهیم.
در حقیقیت میخوایم متد EditorFor را طوری سفارشی سازی کنیم که برای نوع شمارشی Genre، به صورت خودکار یک لیست ایجاد کرده و برگرداند. از نسخهی سوم ASP.NET MVC به بعد این امکان برای توسعه دهندهها فراهم شدهاست. شما میتوانید در پوشهی Shared داخل پوشه Views برنامه، پوشهای را به اسم EditorTemplates ایجاد کنید؛ همینطور DisplayTemplates و برای نوع خاصی که میخواهید سفارشیسازی را برای آن انجام دهید، یک PartialView بسازید.
Views/Shared/DisplayTemplates/<type>.cshtml
کاری که الان میخواهیم انجام دهیم این است که یک SelectListItem ایجاد کرده تا مقدارهای نوع Genreمان داخلش باشد و بتوانیم به راحتی برای ساختن DropDownList از آن استفاده کنیم. برای این کار Helper مخصوص خودمان را ایجاد میکنیم. پوشهای به اسم Helpers در کنار پوشههای Controllers، Models ایجاد میکنیم و در داخل آن کلاسی به اسم EnumHelpers میسازیم.
public static class EnumHelpers { public static IEnumerable<SelectListItem> GetItems( this Type enumType, int? selectedValue) { if (!typeof(Enum).IsAssignableFrom(enumType)) { throw new ArgumentException("Type must be an enum"); } var names = Enum.GetNames(enumType); var values = Enum.GetValues(enumType).Cast<int>(); var items = names.Zip(values, (name, value) => new SelectListItem { Text = GetName(enumType, name), Value = value.ToString(), Selected = value == selectedValue } ); return items; } static string GetName(Type enumType, string name) { var result = name; var attribute = enumType .GetField(name) .GetCustomAttributes(inherit: false) .OfType<DisplayAttribute>() .FirstOrDefault(); if (attribute != null) { result = attribute.GetName(); } return result; } }
در توضیح کد بالا عنوان کرد که متدها بهصورت متدهای الحاقی به نوع Type نوشته شدند. کار خاصی در بدنهی متدها انجام نشدهاست. در بدنهی متد اول لیست آیتمها را تولید کردیم. در هنگام ساخت SelectListItem برای گرفتن Text، متد GetName را صدا زدیم. برای اینکه بتوانیم مقدار ویژگی Display که در هنگام تعریف نوع شمارشی استفاده کردیم را بدست بیاریم، باید چک کنیم ببینیم که آیا این آیتم به این ویژگی مزین شدهاست یا نه. اگر شده بود مقدار را میگیریم و به خصوصیت Text متد اول انتساب میدهیم.
@using Book.Entities @using Book.Web.Helpers @{ var items = typeof(Genre).GetItems((int?)Model); } @Html.DropDownList("", items, new {@class="form-control"})
کدهایی که در بالا مشاهده میکنید کدهایی میباشند که قرار است داخل PartialViewی Genre قرار دهیم که در پوشهی EditorTemplates ساختیم. ابتدا آمدیم آیتمها را گرفتیم و بعد به DropDownList دادیم تا لیست نوع را برای ما بسازد. حالا اگه برنامه را اجرا کنید میبینید که EditorFor برای شما یه لیست از نوع شمارشی ساخته و حالا قابل استفاده هست.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
<HubName("chat")> Public Class ChatHub Inherits Hub Public Sub sendMessage(msg As String) Clients.All.getMessage(msg) 'Call getMessage function client side End Sub End Class
Public Class ActivityInfo Sub New(connectionId As String) Me.ConnectionID = connectionId Me.Time = Now End Sub Public Property ConnectionID As String Public Property Time As DateTime End Class
Public Class SpamDetectionPiplelineModule Inherits HubPipelineModule Public Property SpamDetection As New HashSet(Of ActivityInfo) Private _SpamDetectionLock As New Object Public Function IsSpam(ConnectionId As String) As Boolean SyncLock _SpamDetectionLock 'Remove all old info before 3 seconds ago SpamDetection.RemoveWhere(Function(q) q.Time < Now.AddSeconds(-3)) SpamDetection.Add(New ActivityInfo(ConnectionId)) 'Check activities from 3 seconds ago If SpamDetection.Where(Function(q) q.ConnectionID = ConnectionId).Count > 2 Then Return True Else Return False End If End SyncLock End Function Protected Overrides Function OnBeforeIncoming(context As IHubIncomingInvokerContext) As Boolean If IsSpam(context.Hub.Context.ConnectionId) Then Return False End If Return MyBase.OnBeforeIncoming(context) End Function End Class
Public Class Startup Public Sub Configuration(app As IAppBuilder) GlobalHost.HubPipeline.AddModule(New SpamDetectionPiplelineModule) app.MapSignalR End Sub End Class
احتمالا هزاران بار در کدهای خود چنین قطعه کدی را تکرار کردهاید:
try { // code } catch(Exception ex) { // do something }
void Execute(Action action) { try { action(); } catch(Exception ex) { // log errors } }
Execute(() => {open a file});
یا اگر عمل انجام شده باید خروجی خاصی را بازگرداند (برخلاف یک Action که خروجی از آن انتظار نمیرود)، میتوان طراحی متد Execute را با Func انجام داد:
public static class SafeExecutor { public static T Execute<T>(Func<T> operation) { try { return operation(); } catch (Exception ex) { // Log Exception } return default(T); } }
var data = SafeExecutor.Execute<string>(() => { // do something return "result"; });
از همین دست میتوان به کپسوله سازی منطق «سعی مجدد» در انجام کاری اشاره کرد:
public static class RetryHelper { public static void RetryOperation(Action action, int numRetries, int retryTimeout) { if( action == null ) throw new ArgumentNullException("action"); do { try { action(); return; } catch { if( numRetries <= 0 ) throw; else Thread.Sleep( retryTimeout ); } } while( numRetries-- > 0 ); } }
RetryHelper.RetryOperation(() => SomeFunction(), 3, 1000);
روشهای متفاوت ایجاد توالی (sequence) در Rx
الف) استفاده از متدهای Factory
1) Observable.Create
نمونهای از استفاده از آنرا در مطلب «معرفی Reactive extensions» مشاهده کردید.
var query = Enumerable.Range(1, 5).Select(number => number); var observableQuery = query.ToObservable(); var observer = Observer.Create<int>(onNext: number => Console.WriteLine(number)); observableQuery.Subscribe(observer);
البته در این مثال فقط delegate مربوط به onNext را ملاحظه میکند. توسط سایر overloadهای آن امکان ذکر delegateهای OnError/OnCompleted نیز وجود دارد.
2) Observable.Return
برای ایجاد یک خروجی Observable از یک مقدار مشخص، میتوان از متد جنریک Observable.Return استفاده کرد. برای مثال:
var observableValue1 = Observable.Return("Value"); var observableValue2 = Observable.Return(2);
public static IObservable<T> Return<T>(T value) { return Observable.Create<T>(o => { o.OnNext(value); o.OnCompleted(); return Disposable.Empty; }); }
var observableValue1 = Observable.Return<string>("Value"); var observableValue2 = Observable.Return<int>(2);
3) Observable.Empty
برای بازگشت یک توالی خالی که تنها کار اطلاع رسانی onCompleted را انجام میدهد.
var emptyObservable = Observable.Empty<string>();
public static IObservable<T> Empty<T>() { return Observable.Create<T>(o => { o.OnCompleted(); return Disposable.Empty; }); }
4) Observable.Never
برای بازگشت یک توالی بدون قابلیت اطلاع رسانی و notification
var neverObservable = Observable.Never<string>();
public static IObservable<T> Never<T>() { return Observable.Create<T>(o => { return Disposable.Empty; }); }
5) Observable.Throw
برای ایجاد یک توالی که صرفا کار اطلاع رسانی OnError را توسط استثنای معرفی شده به آن انجام میدهد.
var throwObservable = Observable.Throw<string>(new Exception());
public static IObservable<T> Throws<T>(Exception exception) { return Observable.Create<T>(o => { o.OnError(exception); return Disposable.Empty; }); }
6) توسط Observable.Range
به سادگی میتوان بازهی Observable ایی را ایجاد کرد:
var range = Observable.Range(10, 15); range.Subscribe(Console.WriteLine, () => Console.WriteLine("Completed"));
7) Observable.Generate
اگر بخواهیم عملیات Observable.Range را پیاده سازی کنیم، میتوان از متد Observable.Generate استفاده کرد:
public static IObservable<int> Range(int start, int count) { var max = start + count; return Observable.Generate( initialState: start, condition: value => value < max, iterate: value => value + 1, resultSelector: value => value); }
8) Observable.Interval
عموما از انواع و اقسام تایمرهای موجود در دات نت مانند System.Timers.Timer ، System.Threading.Timer و System.Windows.Threading.DispatcherTimer برای ایجاد یک توالی از رخدادها استفاده میشود. تمام اینها را به سادگی میتوان توسط متد Observable.Interval، که قابل انتقال به تمام پلتفرمهایی است که Rx برای آنها تهیه شدهاست، جایگزین کرد:
var interval = Observable.Interval(period: TimeSpan.FromMilliseconds(250)); interval.Subscribe(Console.WriteLine, () => Console.WriteLine("completed"));
Overload دوم این متد، امکان معرفی scheduler و اجرای بر روی تردی دیگر را نیز میسر میکند.
9) Observable.Timer
تفاوت Observable.Timer با Observable.Interval در مفهوم پارامتر ارسالی به آنها است:
var timer = Observable.Timer(dueTime: TimeSpan.FromSeconds(1)); timer.Subscribe(Console.WriteLine, () => Console.WriteLine("completed"));
خروجی Observable.Interval مثال زده شده به نحو زیر است و خاتمهای ندارد:
0
1
2
3
4
5
اما خروجی Observable.Timer به نحو ذیل بوده و پس از یک ثانیه، خاتمه مییابد:
0
completed
متد Observable.Timer دارای هفت overload متفاوت است که توسط آنها dueTime (مدت زمان صبر کردن تا تولید اولین خروجی)، period (کار Observable.Timer را به صورت متوالی در بازهی زمانی مشخص شده تکرار میکند) و scheduler (تعیین ترد اجرایی عملیات) قابل مقدار دهی هستند.
اگر میخواهید Observable.Timer بلافاصله شروع به کار کند، مقدار dueTime آنرا مساوی TimeSpan.Zero قرار دهید. به این ترتیب یک Observable.Interval را به وجود آوردهاید که بلافاصله شروع به کار کرده است و تا مدت زمان مشخص شدهای جهت اجرای اولین callback خود صبر نمیکند.
ب) تبدیلگرهایی که خروجی IObservable ایجاد میکنند
برای تبدیل مدلهای برنامه نویسی Async قدیمی دات نت مانند APM، رخدادها و امثال آن به معادلهای Rx، متدهای الحاقی خاصی تهیه شدهاند.
1) تبدیل delegates به معادل Observable
متد Observable.Start، امکان تبدیل یک Func یا Action زمانبر را به یک توالی observable میسر میکند. در این حالت به صورت پیش فرض، پردازش عملیات بر روی یکی از تردهای ThreadPool انجام میشود.
static void StartAction() { var start = Observable.Start(() => { Console.Write("Observable.Start"); for (int i = 0; i < 10; i++) { Thread.Sleep(100); Console.Write("."); } }); start.Subscribe( onNext: unit => Console.WriteLine("published"), onCompleted: () => Console.WriteLine("completed")); } static void StartFunc() { var start = Observable.Start(() => { Console.Write("Observable.Start"); for (int i = 0; i < 10; i++) { Thread.Sleep(100); Console.Write("."); } return "value"; }); start.Subscribe( onNext: Console.WriteLine, onCompleted: () => Console.WriteLine("completed")); }
زمانیکه از Func استفاده میشود، تابع یک خروجی را ارائه داده و سپس توالی خاتمه مییابد. اگر از Action استفاده شود، نوع Observable بازگشت داده شده از نوع Unit است که در برنامه نویسی functional معادل void است و هدف از آن مشخص سازی پایان عملیات Action میباشد. Unit دارای مقداری نبوده و صرفا سبب اجرای اطلاع رسانی OnNext میشود.
تفاوت مهم Observable.Start و Observable.Return در این است که Observable.Start مقدار تابع را به صورت تنبل (lazily) پردازش میکند، اما Observable.Return پردازش حریصانهای (eagrly) را به همراه خواهد داشت. به این ترتیب Observable.Start بسیار شبیه به یک Task (پردازشهای غیرهمزمان) عمل میکند.
در اینجا شاید این سؤال مطرح شود که استفاده از قابلیتهای Async سیشارپ 5 برای اینگونه کارها مناسب است یا Rx؟ قابلیتهای Async بیشتر به اعمال مخصوص IO bound مانند کار با شبکه، دریافت فایل از اینترنت، کار با یک بانک اطلاعاتی خارج از مرزهای سیستم، مرتبط میشوند؛ اما اعمال CPU bound مانند محاسبات سنگین حاصل از توالیهای observable را به خوبی میتوان توسط Rx مدیریت کرد.
2) تبدیل Events به معادل Observable
دات نت از روزهای اول خود به همراه یک event driven programming model بودهاست. Rx متدهایی را برای دریافت یک رخداد و تبدیل آن به یک توالی Observable ارائه دادهاست. برای نمونه ObservableCollection زیر را درنظر بگیرید
var items = new System.Collections.ObjectModel.ObservableCollection<string> { "Item1", "Item2", "Item3" };
items.CollectionChanged += (sender, ea) => { if (ea.Action == NotifyCollectionChangedAction.Remove) { foreach (var oldItem in ea.OldItems.Cast<string>()) { Console.WriteLine("Removed {0}", oldItem); } } };
var removals = Observable.FromEventPattern<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs> ( addHandler: handler => items.CollectionChanged += handler, removeHandler: handler => items.CollectionChanged -= handler ) .Where(e => e.EventArgs.Action == NotifyCollectionChangedAction.Remove) .SelectMany(c => c.EventArgs.OldItems.Cast<string>()); var disposable = removals.Subscribe(onNext: item => Console.WriteLine("Removed {0}", item));
items.Remove("Item1");
3) تبدیل Task به معادل Observable
متد ToObservable واقع در فضای نام System.Reactive.Threading.Tasks را بر روی یک Task نیز میتوان فراخوانی کرد:
var task = Task.Factory.StartNew(() => "Test"); var source = task.ToObservable(); source.Subscribe(Console.WriteLine, () => Console.WriteLine("completed"));
4) تبدیل IEnumerable به معادل Observable
با این مورد تاکنون آشنا شدهاید. فقط کافی است متد ToObservable را بر روی یک IEnumerable، جهت تهیه خروجی Observable فراخوانی کرد.
5) تبدیل APM به معادل Observable
APM یا Asynchronous programming model، همان روش کار با متدهای Async با نامهای BeginXXX و EndXXX است که از نگارشهای آغازین دات نت به همراه آن بودهاند. کار کردن با آن مشکل است و مدیریت آن به همراه پراکندگیهای بسیاری جهت کار با callbacks آن است. برای تبدیل این نوع روش برنامه نویسی به روش Rx نیز متدهایی پیش بینی شدهاست؛ مانند Observable.FromAsyncPattern.
یک نکته
کتابخانهای به نام Rxx بسیاری از این محصور کنندهها را تهیه کردهاست:
http://Rxx.codeplex.com
ابتدا بستهی نیوگت آنرا نصب کنید:
PM> Install-Package Rxx
using (new FileStream("file.txt", FileMode.Open) .ReadToEndObservable() .Subscribe(x => Console.WriteLine(x.Length))) { Console.ReadKey(); }
یکی از مشکلاتی که من همیشه با کاربران عادی دارم بحث انتقال مطالب از Word مایکروسافت به ادیتورهای WYSWING تحت وب است. برای مثال شما سایت پویایی را درست کردهاید که کاربران میتوانند مطالب آنرا ویرایش یا کم و زیاد کنند.
اگر مطلب از ابتدا در این نوع ادیتورها تایپ و آماده شود هیچ مشکلی وجود نخواهد داشت چون خروجی اکثر آنها استاندارد است، اما متاسفانه خروجی وب word بسیار مشکلزا است (copy/paste معمولی مطالب آن در یک ادیتور تحت وب) و خصوصا برای نمایش تایپ فارسی در وب اصلا مناسب نیست. یعنی هیچ الزامی وجود ندارد که اندازه فونتها در متن نهایی نمایش داده شده در وب یکسان باشند یا خطوط در هم فرو نروند و یا عدم تناسب اندازه قلم متن صفحه با قلم استفاده شده در CSS سایت (که شکل ناهماهنگ و غیرحرفهای را حاصل خواهد کرد) و امثال آن. اینجاست که کار شما زیر سؤال میرود! "این برنامه درست کار نمیکنه! متن من بههم ریخته شده و امثال این"
این کاربر عادی عموما یک تایپیست است یا یک منشی که به او گفته شده است شما از امروز موظفید مطالبی را در این سایت قرار دهید. بنابراین این کاربر حتما از word استفاده خواهد کرد (برای پیش نویس مطالب). همچنین عموما هم مرورگر "سازمانی" مورد استفاده، هنوز که هنوز است همان IE6 است (در اکثر شرکتها و خصوصا ادارات) و مهم نیست که الان آخرین نگارش IE یا فایرفاکس و تمام هیاهوهای مربوطه به کجا ختم شدهاند. حتما باید سایت با IE6 هم سازگار باشد. بنابراین از برنامه IE tester غافل نشوید.
و دست آخر شما هم نمیتوانید به کاربر عادی ثابت کنید که این خروجی وب word اصلا استاندارد نیست (حتما کار شما است که مشکل دارد نه شرکت معظم مایکروسافت!). یا اینکه به آنها بگوئید اصلا مجاز نیستید در وب همانند یک فایل word از چندین نوع قلم مختلف فارسی غیراستاندارد استفاده کنید چون ممکن است کاربری این نوع قلم مورد استفاده شما را نداشته باشد و نمایش نهایی به هم ریختهتر از آنی خواهد بود که شما فکرش را میکنید! یا اینکه با استفاده از این روش حجم نهایی صفحه حداقل 50 کیلو بایت بیشتر خواهد شد (بدلیل حجم بالای تگهای زاید word) و نباید کاربران دایال آپ را فراموش کرد.
مدتی در اینباره جستجو کردم و نتیجه حاصل این بود که تمامی روشها به یک مورد ختم میشود: حذف تگهای غیراستاندارد word هنگام دریافت مطلب و پیش از ذخیره سازی آن در دیتابیس
یک سری از ادیتورهای متنی تحت وب مانند FCK editor این قابلیت را به صورت خودکار اضافه کردهاند و حتی اگر کاربر متنی را از word در آنها Paste کند پیغامی را در همین رابطه دریافت خواهد کرد (شکل زیر) و البته کاربر میتواند گزینه لغو یا خیر را نیز انتخاب کند و دوباره همان وضعیت قبل تکرار خواهد شد. (یا حتی دکمه مخصوص کپی از word را هم به نوار ابزار خود اضافه کردهاند)
برای این منظور تابع زیر تهیه شدهاست که من همواره از آن استفاده میکنم و تا به امروز مشکل پاسخ پس دادن به کاربران عادی را به این صورت حل کردهام!
این تابع تمامی تگهای اضافی و غیراستاندارد word متن دریافتی از یک ادیتور WYSWING را حذف میکند و به این صورت متن نهایی نمایش داده شده در سایت، تابع CSS مورد استفاده در سایت خواهد شد و نه حجم بالایی از تگهای غیراستاندارد word. (ممکن است کاربر در ابتدا کمی جا بخورد ولی مهم نیست! سایت باید استاندارد نمایشی خودش را از CSS آن دریافت کند و نه از تگهای word)
using System.Text.RegularExpressions;
/// <summary>
/// Removes all FONT and SPAN tags, and all Class and Style attributes.
/// Designed to get rid of non-standard Microsoft Word HTML tags.
/// </summary>
public static string CleanMSWordHtml(string html)
{
try
{
// start by completely removing all unwanted tags
html = Regex.Replace(html, @"<[/]?(font|span|xml|del|ins|[ovwxp]:\w )[^>]*?>", "", RegexOptions.IgnoreCase);
// then run another pass over the html (twice), removing unwanted attributes
html = Regex.Replace(html, @"<([^>]*)(?:class|lang|style|size|face|[ovwxp]:\w )=(?:'[^']*'|""[^""]*""|[^\s>] )([^>]*)>", "<$1$2>", RegexOptions.IgnoreCase);
html = Regex.Replace(html, @"<([^>]*)(?:class|lang|style|size|face|[ovwxp]:\w )=(?:'[^']*'|""[^""]*""|[^\s>] )([^>]*)>", "<$1$2>", RegexOptions.IgnoreCase);
return RemoveHTMLComments(html);
}
catch
{
return html;
}
}
public static string RemoveHTMLComments(string html)
{
try
{
Regex _Regex = new Regex("((<!-- )((?!<!-- ).)*( -->))(\r\n)*", RegexOptions.Singleline);
return _Regex.Replace(html, string.Empty);
}
catch
{
return html;
}
}
برای ارتقاء به نگارش RC0، این مراحل را باید طی کنید:
1) پیش از هر کاری، پوشهی node_modules قدیمی خود را حذف کنید (با تمام محتوای آن).
2) به روز رسانی فایل package.json به صورت ذیل:
{ "name": "asp-net-mvc5x-angular2x", "version": "1.0.0", "author": "DNT", "description": "", "scripts": { "postinstall": "typings install" }, "license": "Apache-2.0", "dependencies": { "@angular/common": "^2.0.0-rc.0", "@angular/compiler": "^2.0.0-rc.0", "@angular/core": "^2.0.0-rc.0", "@angular/http": "2.0.0-rc.0", "@angular/router": "2.0.0-rc.0", "@angular/router-deprecated": "^2.0.0-rc.0", "@angular/platform-browser": "^2.0.0-rc.0", "@angular/platform-browser-dynamic": "^2.0.0-rc.0", "bootstrap": "^3.3.6", "es6-promise": "^3.1.2", "es6-shim": "^0.35.0", "jquery": "^2.2.3", "reflect-metadata": "^0.1.3", "rxjs": "^5.0.0-beta.6", "systemjs": "^0.19.27", "zone.js": "^0.6.12" }, "devDependencies": { "typescript": "^1.8.9", "typings": "^0.8.1" }, "repository": { } }
3) افزودن فایلی به نام typings.json در ریشهی پروژه؛ با این محتوا:
{ "ambientDependencies": { "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#7de6c3dd94feaeb21f20054b9f30d5dabc5efabd" } }
es6-shim.d.ts
بدون این فایل، کامپایلر TypeScript تعاریف ES 6 را مانند Map و Promise و امثال آن، نمیشناسد و پروژه را کامپایل نخواهد کرد.
اکنون یکبار فایل package.json را ذخیره کنید تا کار به روز رسانی بستهها انجام شود. البته اگر بر روی این فایل کلیک راست کنید، در منوی ظاهر شده، گزینهی restore packages هم موجود است.
4) پس از آن، چند تغییر جزئی ذیل باید در کدهای این سری، اعمال شوند:
هر جایی angular2 تعریف شده، اینبار میشود angular@. مثلا:
import { PipeTransform, Pipe } from '@angular/core';
import { RouteParams, Router } from '@angular/router-deprecated';
/// <reference path="../typings/es6-shim.d.ts" /> import { bootstrap } from '@angular/platform-browser-dynamic'; // Our main component import { AppComponent } from "./app.component"; bootstrap(AppComponent);
یک نکته: اگر میخواهید این تعاریف را در یک فایل razor، وارد کنید، چون @ به ابتدای پوشهی angular2 اضافه شده (node_modules\@angular)، مشکل پردازشی razor را ایجاد خواهد کرد و باید escape شود. به همین جهت بجای @ بهتر است معادل آن را یعنی ("@")Html.Raw@ وارد کنید.
سپس ابتدا فایل systemjs.config.js را از اینجا دریافت کنید.
در ادامه مداخل جدید را هم در فایل index.html مثال رسمی آغازین آن بررسی کنید.
بنابراین، فایل systemjs.config.js را به ریشهی سایت اضافه کنید (از این جهت که مسیر بستههای node_modules را از ریشهی سایت میخواند). سپس فایل Views\Shared\_Layout.cshtml را به نحو ذیل تغییر دهید:
<!DOCTYPE html> <html> <head> <base href="/"> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title - My ASP.NET Application</title> <link href="~/node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet"/> <link href="~/app/app.component.css" rel="stylesheet"/> <link href="~/Content/Site.css" rel="stylesheet" type="text/css"/> <!-- 1. Load libraries --> <!-- IE required polyfills, in this exact order --> <script src="~/node_modules/es6-shim/es6-shim.min.js"></script> <script src="~/node_modules/zone.js/dist/zone.js"></script> <script src="~/node_modules/reflect-metadata/Reflect.js"></script> <script src="~/node_modules/systemjs/dist/system.src.js"></script> <script src="~/systemjs.config.js"></script> <!-- 2. Configure SystemJS --> <script> System.import('app/main').then(null, console.error.bind(console)); </script> </head> <body> <div> @RenderBody() <pm-app>Loading App...</pm-app> </div> @RenderSection("Scripts", required: false) </body> </html>
خلاصهی سریع این موارد
الف) تغییرات آخرین بستههای npm را از مستندات آن پیگیری و اعمال کنید. آخرین نگارش آن همیشه در اینجا قابل دسترسی است.
ب) تغییرات index.html، فایل main.ts و مداخل آغازین آنرا از مثال آغازین رسمی آن پیگیری و اعمال کنید.
طریقه بررسی صحت کدملی به کمک متدهای الحاقی
من از الگوریتم زیر استفاده میکنم گفتم اینجا بنویسم شاید به کار بیاد
Private Function ValidateNcode(ByVal NC As Int64) As Boolean If IsNumeric(NC) = False Or NC.ToString.Length < 10 Then Return False End If Dim vnc As String = NC.ToString Dim pos As Integer = 10 Dim sum As Integer = 0 For i = 0 To 8 sum += Integer.Parse(vnc.ToCharArray()(i).ToString()) * pos pos = pos - 1 Next i Dim remind As Integer = sum Mod 11 Dim controlNum As Integer = Integer.Parse(vnc.ToCharArray()(9)) If remind <= 2 Then If controlNum = remind Then Return True Else Return False End If Else If (11 - remind) = controlNum Then Return True Else Return False End If End If End Function
private bool ValidateNcode(Int64 NC) { if (IsNumeric(NC) == false | NC.ToString.Length < 10) { return false; } string vnc = NC.ToString; int pos = 10; int sum = 0; for (i = 0; i <= 8; i++) { sum += int.Parse(vnc.ToCharArray()(i).ToString()) * pos; pos = pos - 1; } int remind = sum % 11; int controlNum = int.Parse(vnc.ToCharArray()(9)); if (remind <= 2) { if (controlNum == remind) { return true; } else { return false; } } else { if ((11 - remind) == controlNum) { return true; } else { return false; } }
var user = _documentSession .Include<User>(x => x.Apps[59].AddressId) .Load("Users/131-A"); var address = _documentSession.Load<Address>(user.Apps[59].AddressId)
var user = _documentSession .Include<User>(x => x.Apps.Values.Select(app => app.AddressId)) .Load("Users/131-A"); var addresses = List<Address>(); foreach(app in user.Apps) { addresses.Add(_documentSession.Load<Address>(app.AddressId)); //queryسمت کلاینت انجام اجرا میشود }
- حتما باید Id سند(ها) را داشته باشیم.
- کل سند را بازیابی میکند.
List<User> users = await _documentSession .Query<Users>() .Where(u => u.PhoneNumber.StartsWith("915")) .ToListAsync();
var users = await _documentSession.Query<AppUser>() .Where(u => u.Id.Equals("915")) .Select(u => new { u.Apps[appCode].FirstName, u.Apps [appCode].LastName, }) .ToListAsync();
from Users as user where startsWith(user.PhoneNumber, "915") select { FirstName : user.Apps ["59"].FirstName, LastName : user.Apps ["59"].LastName }
from u in _documentSession.Query<User>() where u.PhoneNumber.StartsWith("915") let app = u.Apps["59"] select new { app.FirstName, app.LastName, };
declare function output(u) { var app = u.Apps["59"]; return { FirstName : app.FirstName, LastName : app.LastName}; } from Users as user where startsWith(user.PhoneNumber, "915") select output(user)
app.FirstName, app.LastName, *key = u.ActiveInApps.Select(a => a.Key)
query = query.Search(u => u.key, "59");
public class User_MyIndex : AbstractIndexCreationTask<User> { Map = users => from u in users from app in u.Apps select new { Id = u.Id, PhoneNumber = u.PhoneNumber, UserName = app.Value.UserName, FirstName = app.Value.FirstName, LastName = app.Value.LastName, IsActive = app.Value.IsActive, key = app.Key }; }
new User_MyIndex().Execute(store);
IndexCreation.CreateIndexes(typeof(User_MyIndex).Assembly, store);
from u in _documentSession.Query<User, User_MyIndex>() ...
select new { ... key = aia.Key, Address = LoadDocument<Address>(aia.Value.AddressId), // City = LoadDocument<Address>(aia.Value.AddressId).City, };
Message = app.Messages.Select(m => LoadDocument<Message>(m).Content)
var users = _documentSession.Advanced.AsyncDocumentQuery<User, User_MyIndex>() .WhereStartsWith(nameof(AppUser.PhoneNumber), "915") .WhereEquals("key", appCode, exact: true) .SelectFields<AppUserModel>(new[] { $"Apps[{appCode}].FirstName", $"Apps[{appCode}].LastName" }) .ToListAsync();
public class Post { public int Id { get; set; } public string Content { get; set; } public string Title { get; set; } public List<string> Tags { get; set; } public string WriterName { get; set; } public string WriterId { get; set; } }
public class Post_ByContent : AbstractIndexCreationTask<Post> { public Post_ByContent() { Map = posts=> from post in posts select new { post.Content }; Analyzers.Add(p => p.Content, "StandardAnalyzer"); } }
List<Post> posts = _documentSession .Query<Post, Post_ByContent>() .MoreLikeThis(builder => builder .UsingDocument(p => p.Id == "posts/59-A") .WithOptions(new MoreLikeThisOptions { Fields = new[] { nameof(Post.Content) }, StopWordsDocumentId = "appConfig/StopWords" })) .ToList();