Optimizing ASP.NET MVC view lookup performance
همان طور که در انتهای مقاله اشاره شده است، استفاده از یک ConcurrentDictionary میتواند کارایی خوبی داشته باشد اما خوب استاتیک است و به حذف و اضافه شدن فیزیکی Viewها حساس نیست.
npx create-react-app tssample --template typescript
import React from "react"; export const Head = () => { return ( <div> <h1>Hello</h1> </div> ); };
import { Head } from "./components/Head"; // ... function App() { return ( <div className="App"> <Head /> // ... </div> ); } export default App;
import React from "react"; export const Head = ({ title, isActive }) => { return ( <div> <h1>{title}</h1> {isActive && <h3>Active</h3>} </div> ); };
{ "compilerOptions": { "strict": true /* Enable all strict type-checking options. */ } }
{ "compilerOptions": { "strict": true, "noImplicitAny": false } }
type Props = { title: string; isActive: boolean; };
export const Head = ({ title, isActive }: Props) => {
Head.propTypes = { title: PropTypes.string, isActive: PropTypes.bool }
<Head title="Hello" isActive={true} />
<title>@ViewBag.Title</title> @RenderBody()
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <link href="~/Content/Site.css" rel="stylesheet" /> <script src="~/Scripts/jquery-1.8.2.min.js"></script> <script src="~/Scripts/jquery.pjax.js"></script> <script type="text/javascript"> $(function () { $(document).pjax('a[withpjax]', '#pjaxContainer', { timeout: 5000 }); }); </script> </head> <body> <div>Main layout ...</div> <div id="pjaxContainer"> @RenderBody() </div> </body> </html>
@{ if (Request.Headers["X-PJAX"] != null) { Layout = "~/Views/Shared/_PjaxLayout.cshtml"; } else { Layout = "~/Views/Shared/_Layout.cshtml"; } }
using System.Web.Mvc; namespace PajxMvcApp.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } public ActionResult About() { return View(); } } }
@{ ViewBag.Title = "Index"; } <h2>Index</h2> @Html.ActionLink(linkText: "About", actionName:"About", routeValues: null, controllerName:"Home", htmlAttributes: new { withpjax = "with-pjax"})
using Parbad.Builder; public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddParbad() // .configurations // .configurations // .configurations }
using Parbad.Builder; public class Startup { public void Configuration(IAppBuilder app) { ParbadBuilder.CreateDefaultBuilder() // .configurations // .configurations // .configurations } }
services.AddParbad() .ConfigureGateways(gateways => { gateways .AddMellat() .WithOptions(options => { options.TerminalId = 123; options.UserName = "MyId"; options.UserPassword = "MyPassword"; }); });
public class MellatOptionsProvider : IParbadOptionsProvider<MellatGatewayOptions> { private readonly IMySettingsService _settingsService; public MellatOptionsProvider(IMySettingsService settingsService) { _settingsService = settingsService; } public void Provide(MellatGatewayOptions options) { var settings = _settingsService.GetSettings(); options.TerminalId = settings.TerminalId; options.UserName = settings.UserName; options.UserPassword = settings.UserPassword; } }
services.AddParbad() .ConfigureGateways(gateways => { gateways .AddMellat() .WithOptionsProvider<MellatOptionsProvider>(ServiceLifetime.Transient); });
services.AddParbad() .ConfigureGateways(gateways => { gateways .AddMellat() .WithConfiguration(IConfiguration.GetSection("Mellat"); });
"Mellat": { "TerminalId": 123, "UserName": "MyUsername", "UserPassword": "MyPassword" }
ParbadBuilder.CreateDefaultBuilder() .ConfigureHttpContext(builder => builder.UseOwinFromCurrentHttpContext());
services.AddParbad() .ConfigureHttpContext(builder => builder.UseDefaultAspNetCore());
services.AddParbad() .ConfigureStorage(builder => builder.UseParbadSqlServer("ConnectionString"));
services.AddParbad() .ConfigureStorage(builder => builder.UseInMemoryDatabase("MyMemoryName"));
services.AddParbad() .ConfigureMessages(options => { options.PaymentSucceed = "Payment was successful."; options.PaymentFailed = "Payment was not successful."; // other messages... });
<button onClick={this.handleReset} className="btn btn-primary btn-sm m-2" > Reset </button>
handleReset = () => { const counters = this.state.counters.map(counter => { counter.value = 0; return counter; }); this.setState({ counters }); // = this.setState({ counters: counters }); };
class Counter extends Component { state = { count: this.props.counter.value };
handleIncrement = () => { this.setState({ count: this.state.count + 1 }); };
<button onClick={() => this.props.onIncrement(this.props.counter)} className="btn btn-secondary btn-sm" > Increment </button>
getBadgeClasses() { let classes = "badge m-2 badge-"; classes += this.props.counter.value === 0 ? "warning" : "primary"; return classes; } formatCount() { const { value } = this.props.counter; // Object Destructuring return value === 0 ? "Zero" : value; }
handleIncrement = counter => { console.log("handleIncrement", counter); };
<Counter key={counter.id} counter={counter} onDelete={this.handleDelete} onIncrement={this.handleIncrement} />
handleIncrement = counter => { console.log("handleIncrement", counter); const counters = [...this.state.counters]; // cloning an array const index = counters.indexOf(counter); counters[index] = { ...counter }; // cloning an object counters[index].value++; console.log("this.state.counters", this.state.counters[index]); this.setState({ counters }); };
import App from "./App"; ReactDOM.render(<App />, document.getElementById("root"));
import React, { Component } from "react"; class NavBar extends Component { render() { return ( <nav className="navbar navbar-light bg-light"> <a className="navbar-brand" href="#"> Navbar </a> </nav> ); } } export default NavBar;
import "./App.css"; import React from "react"; import Counters from "./components/counters"; import NavBar from "./components/navbar"; function App() { return ( <React.Fragment> <NavBar /> <main className="container"> <Counters /> </main> </React.Fragment> ); } export default App;
render() { return ( <React.Fragment> <NavBar /> <main className="container"> <Counters counters={this.state.counters} onReset={this.handleReset} onIncrement={this.handleIncrement} onDelete={this.handleDelete} /> </main> </React.Fragment> ); }
render() { return ( <div> <button onClick={this.props.onReset} className="btn btn-primary btn-sm m-2" > Reset </button> {this.props.counters.map(counter => ( <Counter key={counter.id} counter={counter} onDelete={this.props.onDelete} onIncrement={this.props.onIncrement} /> ))} </div> ); }
<NavBar totalCounters={this.state.counters.filter(c => c.value > 0).length} />
class NavBar extends Component { render() { return ( <nav className="navbar navbar-light bg-light"> <a className="navbar-brand" href="#"> Navbar{" "} <span className="badge badge-pill badge-secondary"> {this.props.totalCounters} </span> </a> </nav> ); } }
import React from "react"; // Stateless Functional Component const NavBar = props => { return ( <nav className="navbar navbar-light bg-light"> <a className="navbar-brand" href="#"> Navbar{" "} <span className="badge badge-pill badge-secondary"> {props.totalCounters} </span> </a> </nav> ); }; export default NavBar;
const NavBar = ({ totalCounters }) => {
در WPF و Silverlight میتوان با استفاده از مقید سازی (DataBinding) کنترلها را به منبعهای داده متصل کرد. این منابع به چند شیوه مختلف مانند استفاده مستقیم از خصوصیتSource قابل دسترسی هستند. یکی از این روش ها، ارث بری از DataContext نزدیکترین والد است.
همانطور که گفته شدDataContext هر کنترل، توسط تمامی فرزندان آن قابل دسترسی است. اما در بعضی مواقع، زمانیکه کنترل فرزند، بخشی از visual یا logical tree نباشند، دسترسی به DataContext وجود ندارد.
برای مثال زمانی که نیاز است خصوصیت ItemsSource مربوط به یک به لیستی خارج از ItemsSource کنترل DataGrid DataGridTemplateColumn مثلا به لیستی درون ViewModel مربوط به Window در مثال زیر مقید شود، به صورت معمول باید به این صورت عمل کرد:
ViewModel :
public List<People> ComboBoxDataSource{get; set;}
: XAML
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" x:Name="this"> <Grid> <DataGrid ItemsSource="{Binding DataCollection}"> <DataGrid.Columns> <DataGridComboBoxColumn ItemsSource="{Binding DataContext.ComboBoxDataSource, ElementName=this}"/> </DataGrid.Columns> </DataGrid> </Grid> </Window>
با اینکه همه چیز درست به نظر میرسد اما در عمل هیچ اتصالی صورت نمیگیرد و در پنجره Output ویژوال استادیو خطای زیر مشاهده میشود:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=ComboBoxDataSource; DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=17334644); target property is 'ItemsSource' (type 'IEnumerable')
این خطا مشخص میکند که WPF نمیتواند تشخیص بدهد که کدام FrameWorkElement قرار است از DataContext استفاده کند؛ چرا که همانطور که قبلا عنوان شد DataGridTemplateColumn بخشی از visual یا logical treeنیست.
برای مشکل فوق در صورتیکه خصوصیت مورد نظر، یک خصوصیت از فرزندان کنترل باشد، از طریق استایلها میتوان مشکل را حل کرد. برای مثال به جای ItemSource مربوط به DataGridComboBoxColumn میتوان خصوصیت ItemSource کنترل ComboBox درون آن را تنظیم کرد.
<DataGridComboBoxColumn DisplayMemberPath="FirstName"> <DataGridComboBoxColumn.EditingElementStyle> <Style TargetType="ComboBox"> <Setter Property="ItemsSource" Value="{Binding DataContext.ComboBoxDataSource , ElementName=this}"/> </Style> </DataGridComboBoxColumn.EditingElementStyle> </DataGridComboBoxColumn>
اما در صورتیکه نیاز باشد یک خصوصیت از خود DataGridComboBoxColumn مانند Visibility مقید سازی شود، روش بالا کارساز نخواهد بود. برای حل مشکل فوق میتوان از کلاسهای Freezable استفاده کرد؛ چرا که این کلاسها میتوانند از DataContext ارث بری کنند حتی زمانیکه بخشی از visual یاlogical tree نباشند. برای این کار میتوان کلاس زیر را ایجاد کرد:
public class DataBindingHelper : Freezable { protected override Freezable CreateInstanceCore() { return new DataBindingHelper(); } public object Data { get { return (object)GetValue(DataProperty); } set { SetValue(DataProperty, value); } } public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(DataBindingHelper), new UIPropertyMetadata(null)); }
<DataGrid.Resources> <local:DataBindingHelper x:Key="bindingHelper"Data="{Binding}"/> </DataGrid.Resources>
و هنگام مقید سازی خصوصیت Visibility مربوط به DataGridComboBoxColumn، از نمونه ساخته شده به عنوان Source استفاده نمود.
<DataGridComboBoxColumn Visibility="{Binding Data.IsVisible,Converter={StaticResource visibilityConverter},Source={StaticResource bindingHelper}}"/>
namespace CrMap.Models { public class Symbol { public char Character { set; get; } public string CharacterCode { set; get; } } }
using System; using System.Collections.Generic; using System.Windows.Media; using CrMap.Models; namespace CrMap.ViewModels { public class CrMapViewModel { public IList<Symbol> Symbols { set; get; } public int GridRows { set; get; } public int GridCols { set; get; } public CrMapViewModel() { fillDataSource(); } private void fillDataSource() { Symbols = new List<Symbol>(); GridCols = 15; var fontFamily = new FontFamily(new Uri("pack://application:,,,/"), "/Fonts/#whhglyphs"); GlyphTypeface glyph = null; foreach (var typeface in fontFamily.GetTypefaces()) { if (typeface.TryGetGlyphTypeface(out glyph) && (glyph != null)) break; } if (glyph == null) throw new InvalidOperationException("Couldn't find a GlyphTypeface."); GridRows = (glyph.CharacterToGlyphMap.Count / GridCols) + 1; foreach (var item in glyph.CharacterToGlyphMap) { var index = item.Key; Symbols.Add(new Symbol { Character = Convert.ToChar(index), CharacterCode = string.Format("&#x{0:X}", index) }); } } } }
<Window x:Class="CrMap.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:CrMap.ViewModels" Title="MainWindow" WindowStartupLocation="CenterScreen" WindowState="Maximized" Height="350" Width="525"> <Window.Resources> <vm:CrMapViewModel x:Key="vmCrMapViewModel" /> </Window.Resources> <ScrollViewer VerticalScrollBarVisibility="Visible"> <ItemsControl DataContext="{StaticResource vmCrMapViewModel}" ItemsSource="{Binding Symbols}" Name="MainItemsControl" VerticalAlignment="Top" HorizontalAlignment="Center" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="4"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid HorizontalAlignment="Center" VerticalAlignment="Center" Columns="{Binding GridCols}" Rows="{Binding GridRows}"> </UniformGrid> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <ContentControl> <Border BorderBrush="SlateGray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="1" CornerRadius="3" Margin="3"> <StackPanel Margin="3" Orientation="Vertical"> <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Fonts/#whhglyphs" Foreground="DarkRed" FontSize="17" Text="{Binding Character}" /> <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding CharacterCode}" /> </StackPanel> </Border> </ContentControl> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </ScrollViewer> </Window>
PM> install-package OxyPlot.Wpf
<Window x:Class="OxyPlotWpfTests.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:oxy="http://oxyplot.org/wpf" xmlns:oxyPlotWpfTests="clr-namespace:OxyPlotWpfTests" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <oxyPlotWpfTests:MainWindowViewModel x:Key="MainWindowViewModel" /> </Window.Resources> <Grid DataContext="{Binding Source={StaticResource MainWindowViewModel}}"> <oxy:PlotView Model="{Binding PlotModel}"/> </Grid> </Window>
public class MainWindowViewModel { public PlotModel PlotModel { get; set; }
private void createPlotModel() { PlotModel = new PlotModel { Title = "سری خطوط", Subtitle = "Pan (right click and drag)/Zoom (Middle click and drag)/Reset (double-click)" }; PlotModel.MouseDown += (sender, args) => { if (args.ChangedButton == OxyMouseButton.Left && args.ClickCount == 2) { foreach (var axis in PlotModel.Axes) axis.Reset(); PlotModel.InvalidatePlot(false); } }; }
readonly LinearAxis _xAxis = new LinearAxis(); private void addXAxis() { _xAxis.Minimum = 0; _xAxis.MaximumPadding = 1; _xAxis.MinimumPadding = 1; _xAxis.Position = AxisPosition.Bottom; _xAxis.Title = "X axis"; _xAxis.MajorGridlineStyle = LineStyle.Solid; _xAxis.MinorGridlineStyle = LineStyle.Dot; PlotModel.Axes.Add(_xAxis); }
readonly LinearAxis _yAxis = new LinearAxis(); private void addYAxis() { _yAxis.Minimum = 0; _yAxis.Title = "Y axis"; _yAxis.MaximumPadding = 1; _yAxis.MinimumPadding = 1; _yAxis.MajorGridlineStyle = LineStyle.Solid; _yAxis.MinorGridlineStyle = LineStyle.Dot; PlotModel.Axes.Add(_yAxis); }
readonly LineSeries _lineSeries1 = new LineSeries(); private void addLineSeries1() { _lineSeries1.MarkerType = MarkerType.Circle; _lineSeries1.StrokeThickness = 2; _lineSeries1.MarkerSize = 3; _lineSeries1.Title = "Start"; _lineSeries1.MouseDown += (s, e) => { if (e.ChangedButton == OxyMouseButton.Left) { PlotModel.Subtitle = "Index of nearest point in LineSeries: " + Math.Round(e.HitTestResult.Index); PlotModel.InvalidatePlot(false); } }; PlotModel.Series.Add(_lineSeries1); }
readonly LineSeries _lineSeries2 = new LineSeries(); private void addLineSeries2() { _lineSeries2.MarkerType = MarkerType.Circle; _lineSeries2.Title = "End"; _lineSeries2.StrokeThickness = 2; _lineSeries2.MarkerSize = 3; _lineSeries2.MouseDown += (s, e) => { if (e.ChangedButton == OxyMouseButton.Left) { PlotModel.Subtitle = "Index of nearest point in LineSeries: " + Math.Round(e.HitTestResult.Index); PlotModel.InvalidatePlot(false); } }; PlotModel.Series.Add(_lineSeries2); }
private int _xMax; private int _yMax; private bool _haveNewPoints; private void addPoints() { var timer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(1)}; var rnd = new Random(); var x = 1; updateXMax(x); timer.Tick += (sender, args) => { var y1 = rnd.Next(100); updateYMax(y1); _lineSeries1.Points.Add(new DataPoint(x, y1)); var y2 = rnd.Next(100); updateYMax(y2); _lineSeries2.Points.Add(new DataPoint(x, rnd.Next(y2))); x++; updateXMax(x); _haveNewPoints = true; }; timer.Start(); } private void updateXMax(int value) { if (value > _xMax) { _xMax = value; } } private void updateYMax(int value) { if (value > _yMax) { _yMax = value; } }
private readonly Stopwatch _stopwatch = new Stopwatch(); private void updatePlot() { CompositionTarget.Rendering += (sender, args) => { if (_stopwatch.ElapsedMilliseconds > _lastUpdateMilliseconds + 2000 && _haveNewPoints) { if (_yMax > 0 && _xMax > 0) { _yAxis.Maximum = _yMax + 3; _xAxis.Maximum = _xMax + 1; } PlotModel.InvalidatePlot(false); _haveNewPoints = false; _lastUpdateMilliseconds = _stopwatch.ElapsedMilliseconds; } }; }