کلیه نکات مطلب فوق را به همراه بهبودهای مطرح شده در نظرات آن، در پروژهی ذیل میتوانید به صورت یکجا دریافت و بررسی کنید:
XMLWorkerRTLsample.zip
using System.Web.Mvc;
namespace MvcApplication16.Controllers
{
public class HomeController : Controller
{
[OutputCache(Duration = 60, VaryByParam = "none")]
public ActionResult Index()
{
return View();
}
}
}
using System;
using System.Web.Mvc;
namespace MvcApplication16.Controllers
{
public class HomeController : Controller
{
[OutputCache(Duration = 60, VaryByParam = "none")]
public ActionResult Index()
{
ViewBag.ControllerTime = DateTime.Now;
return View();
}
}
}
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>@ViewBag.ControllerTime</p>
<p>@DateTime.Now</p>
using System;
using System.Web.Mvc;
namespace MvcApplication16.Controllers
{
public class HomeController : Controller
{
[OutputCache(Duration = 60, VaryByParam = "none")]
public ActionResult Index(string parameter)
{
ViewBag.Msg = parameter ?? string.Empty;
ViewBag.ControllerTime = DateTime.Now;
return View();
}
}
}
@{
ViewBag.Title = "Index";
}
<h2>@ViewBag.Msg</h2>
<p>@ViewBag.ControllerTime</p>
<p>@DateTime.Now</p>
@using (Html.BeginForm())
{
@Html.TextBox("parameter")
<input type="submit" />
}
[OutputCache(Duration = 60, VaryByParam = "parameter")]
public ActionResult Index(string parameter)
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
using System;
using System.Web.Mvc;
namespace MvcApplication16.Helper
{
/// <summary>
/// Adds "Cache-Control: private, max-age=0" header,
/// ensuring that the responses are not cached by the user's browser.
/// </summary>
public class NoCachingAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
filterContext.HttpContext.Response.CacheControl = "private";
filterContext.HttpContext.Response.Cache.SetMaxAge(TimeSpan.FromSeconds(0));
}
}
}
<system.web>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name="Aggressive" location="ServerAndClient" duration="300"/>
<add name="Mild" duration="100" location="Server" />
</outputCacheProfiles>
</outputCacheSettings>
</caching>
[OutputCache(CacheProfile = "Aggressive", VaryByParam = "parameter")]
public ActionResult Index(string parameter)
@{ Response.WriteSubstitution(ctx => DateTime.Now.ToShortTimeString()); }
using System;
using System.Web;
using System.Web.Caching;
namespace MvcApplication16.Helper
{
public static class CacheManager
{
public static void CacheInsert(this HttpContextBase httpContext, string key, object data, int durationMinutes)
{
if (data == null) return;
httpContext.Cache.Add(
key,
data,
null,
DateTime.Now.AddMinutes(durationMinutes),
TimeSpan.Zero,
CacheItemPriority.AboveNormal,
null);
}
public static T CacheRead<T>(this HttpContextBase httpContext, string key)
{
var data = httpContext.Cache[key];
if (data != null)
return (T)data;
return default(T);
}
public static void InvalidateCache(this HttpContextBase httpContext, string key)
{
httpContext.Cache.Remove(key);
}
}
}
[Authorize]
[OutputCache(Duration = 60)]
public ActionResult Index()
تصویر فوق مربوط به لینک یک سایت خبری در ادیتور فیسبوک میباشد. همانطور که میبینید عنوان خبر، عکس و توضیح مختصری در مورد خبر، با نظم و ترتیبی خاص نمایش داده شده است.
حال به تصویر زیر که مربوط به لینکی از همین سایت میباشد دقت کنید:
<html itemscope itemtype="http://schema.org/Article">
<title>عنوان صفحه ، حداکثر 60 تا 70 کارکتر باشد</title> <meta name="description" content="شرح صفحه ، حداکثر 150 کارکتر باشد" /> <!-- Schema.org markup for Google+ --> <meta itemprop="name" content="نام یا عنوان صفحه"> <meta itemprop="description" content="شرح صفحه"> <meta itemprop="image" content="نشانی اینترنتی عکسی که در پیشنمایش نشان داده میشود"> <!-- Twitter Card data --> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:site" content="کپی رایت نام سایت"> <meta name="twitter:title" content="نام یا عنوان صفحه"> <meta name="twitter:description" content="شرح صفحه"> <meta name="twitter:creator" content="نویسنده"> <!-- Picture size at least 280x150px -->عکس پیشنمایش با ابعاد حداقل <meta name="twitter:image:src" content="نشانی اینترنتی عکس مطلب"> <!-- Open Graph data --> <meta property="og:title" content="عنوان صفحه" /> <meta property="og:type" content="article" /> <meta property="og:url" content="نشانی سایت" /> <meta property="og:image" content="نشانی عکس مطلب" /> <meta property="og:description" content="شرح مطلب" /> <meta property="og:site_name" content="نام سایت" /> <meta property="article:published_time" content="تاریخ انتشار" /> <meta property="article:modified_time" content="تاریخ بروزرسانی" /> <meta property="article:section" content="نام بخش محتوی متن مقاله" /> <meta property="article:tag" content="نام تگ محتوی متن مقاله" /> <meta property="fb:admins" content="شناسه عددی کاربری شما در فیسبوک" />
<html itemscope itemtype="http://schema.org/Product">
<title>عنوان صفحه</title> <meta name="description" content="شرح صفحه" /> <!-- Schema.org markup for Google+ --> <meta itemprop="name" content="عنوان صفحه"> <meta itemprop="description" content="Tشرح صفحه"> <meta itemprop="image" content="نشانی عکس محصول یا کالا"> <!-- Twitter Card data --> <meta name="twitter:card" content="product"> <meta name="twitter:site" content="کپی رایت سایت"> <meta name="twitter:title" content="عنوان صفحه"> <meta name="twitter:description" content="شرح محصول یا کالا"> <meta name="twitter:creator" content="نویسنده"> <meta name="twitter:image" content="نشانی عکس محصول یا کالا"> <meta name="twitter:data1" content="قیمت محصول یا کالا"> <meta name="twitter:label1" content="Price"> <meta name="twitter:data2" content="رنگ کالا یا محصحول"> <meta name="twitter:label2" content="Color"> <!-- Open Graph data --> <meta property="og:title" content="عنوان صفحه" /> <meta property="og:type" content="article" /> <meta property="og:url" content="نشانی سایت" /> <meta property="og:image" content="عکس محصول یا کالا" /> <meta property="og:description" content="شرح مصحول" /> <meta property="og:site_name" content="نام سایت" /> <meta property="og:price:amount" content="قیمت محصول یا کالا" /> <meta property="og:price:currency" content="واحد ارزی قیمت" />
روش سوم: DTO (Data transfer objects)
در قسمتهای قبلی دو روش از روشهای موجود جهت تبادل دادهها بین لایهها، ذکر گردید و علاوه بر این، مزایا و معایب هر کدام از آنها نیز ذکر شد. در این قسمت دو روش دیگر، به همراه مزایا و معایب آنها برشمرده میشود. لازم به ذکر است هر کدام از این روشها میتواند با توجه به شرایط موجود و نظر طراح نرم افزار، دارای تغییراتی جهت رسیدن به یکسری اهداف و فاکتورها در نرم افزار باشد.
در این روش ما سعی میکنیم طراحی کلاسها را به اصطلاح مسطح ( flatten) کنیم تا بر مشکل double loop که در قسمت قبل بحث کردیم غلبه کنیم. در کد ذیل مشاهده میکنید که چگونه کلاس CusomerDTO از CustomerEntity ، مشتق میشود و کلاس Address را با CustomerEntity ادغام میکند؛ تا برای افزایش سرعت لود و نمایش دادهها، یک کلاس de-normalized شده ایجاد نماید.
public class CustomerDTO : CustomerEntity { public AddressEntity _Address = new AddressEntity(); }
در کد ذیل میتوانید مشاهده کنید که چگونه با استفاده از فقط یک loop یک کلاس de-normalized شده را پر میکنیم.
foreach (DataRow o1 in oCustomers.Tables[0].Rows) { CustomerDTO o = new CustomerDTO(); o.CustomerCode = o1[0].ToString(); o.CustomerName = o1[1].ToString(); o._Address.Address1 = o1[2].ToString(); o._Address.Address2 = o1[3].ToString(); obj.Add(o); }
UI هم به راحتی میتواند DTO را فراخوانی کرده و دیتا را دریافت کند.
مزایا و معایب روش DTO
یکی از
بزرگترین مزایای این روش سرعت زیاد در بارگذاری اطلاعات، به دلیل استفاده کردن از ساختار de-normalized میباشد. اما همین مسئله خود یک عیب محسوب میشود؛ به این دلیل که اصول شئ گرایی را نقض میکند.
روش چهارم: Hybrid approach (Entity + DTO)
از یک طرف کلاسهای Entity که دنیای واقعی را مدل خواهند کرد و همچنین اصول شئ گرایی را رعایت میکنند و از یک طرف دیگر DTO نیز یک ساختار flatten را برای رسیدن به اهداف کارآیی دنبال خواهند کرد. خوب، به نظر میرسد که بهترین کار استفاده از هر دو روش و مزایای آن روشها باشد.
زمانیکه سیستم، اهدافی مانند انجام اعمال CRUD را دنبال میکند و شما میخواهید مطمئن شوید که اطلاعات، دارای integrity میباشند و یا اینکه میخواهید این ساختار را مستقیما به کاربر نهایی ارائه دهید، استفاده کردن از روش (Entity) به عنوان یک روش normalized میتواند بهترین روش باشد. اما اگر میخواهید حجم بزرگی از دیتا را نمایش دهید، مانند گزارشات طولانی، بنابراین استفاده از روش DTO با توجه به اینکه یک روش de-normalized به شمار میرود بهترین روش میباشد.
کدام روش بهتر است؟
Non-uniform : این روش برای حالتی است که متدهای مربوط به data access تغییرات زیادی را تجربه نخواهند کرد. به عبارت دیگر، اگر پروژهی شما در آینده دیتابیسهای مختلفی را مبتنی بر تکنولوژیهای متفاوت، لازم نیست پشتیبانی کند، این روش میتواند بهترین روش باشد.Application Model 00:59:50
Metro and WinRT introduce some significant changes to the world in which applications execute.
This module describes the implications for developers.
Introduction
Application Lifecycle
Demo: Application Lifecycle
Managing State
Demo: Saving State
Splash Screens
Launching Applications
Application Manifest
Packaging
Summary
namespace BlazorTreeView.ViewModels; public class Comment { public IList<Comment> Comments = new List<Comment>(); public string? Text { set; get; } }
using BlazorTreeView.ViewModels; namespace BlazorTreeView.Pages; public partial class TreeView { private IReadOnlyDictionary<string, object> ChildrenHtmlAttributes { get; } = new Dictionary<string, object>(StringComparer.Ordinal) { { "style", "list-style: none;" }, }; private IList<Comment> Comments { get; } = new List<Comment> { new() { Text = "پاسخ یک", }, new() { Text = "پاسخ دو", Comments = new List<Comment> { new() { Text = "پاسخ اول به پاسخ دو", Comments = new List<Comment> { new() { Text = "پاسخی به پاسخ اول پاسخ دو", }, }, }, new() { Text = "پاسخ دوم به پاسخ دو", }, }, }, new() { Text = "پاسخ سوم", }, }; }
/// <summary> /// A custom DntTreeView /// </summary> public partial class DntTreeView<TRecord> {
/// <summary> /// The treeview's self-referencing items /// </summary> [Parameter] public IEnumerable<TRecord>? Items { set; get; }
/// <summary> /// The treeview item's template /// </summary> [Parameter] public RenderFragment<TRecord>? ItemTemplate { set; get; }
/// <summary> /// The content displayed if the list is empty /// </summary> [Parameter] public RenderFragment? EmptyContentTemplate { set; get; }
public class Comment { public IList<Comment> Comments = new List<Comment>(); public string? Text { set; get; } }
/// <summary> /// The property which returns the children items /// </summary> [Parameter] public Expression<Func<TRecord, IEnumerable<TRecord>>>? ChildrenSelector { set; get; }
<DntTreeView TRecord="Comment" Items="Comments" ChildrenSelector="m => m.Comments"
public partial class DntTreeView<TRecord> { private Expression? _lastCompiledExpression; internal Func<TRecord, IEnumerable<TRecord>>? CompiledChildrenSelector { private set; get; } // ... protected override void OnParametersSet() { if (_lastCompiledExpression != ChildrenSelector) { CompiledChildrenSelector = ChildrenSelector?.Compile(); _lastCompiledExpression = ChildrenSelector; } } }
@namespace BlazorTreeView.Pages.Components @typeparam TRecord @if (Items is null || !Items.Any()) { @EmptyContentTemplate } else { <CascadingValue Value="this"> <ul @attributes="AdditionalAttributes"> @foreach (var item in Items) { <DntTreeViewChildrenItem TRecord="TRecord" ParentItem="item"/> } </ul> </CascadingValue> }
@namespace BlazorTreeView.Pages.Components @typeparam TRecord <li @attributes="@SafeOwnerTreeView.ChildrenHtmlAttributes" @key="ParentItem?.GetHashCode()"> @if (SafeOwnerTreeView.ItemTemplate is not null && ParentItem is not null) { @SafeOwnerTreeView.ItemTemplate(ParentItem) } @if (Children is not null) { <ul> @foreach (var item in Children) { <DntTreeViewChildrenItem TRecord="TRecord" ParentItem="item"/> } </ul> } </li>
/// <summary> /// A custom DntTreeView /// </summary> public partial class DntTreeViewChildrenItem<TRecord> { /// <summary> /// Defines the owner of this component. /// </summary> [CascadingParameter] public DntTreeView<TRecord>? OwnerTreeView { get; set; } private DntTreeView<TRecord> SafeOwnerTreeView => OwnerTreeView ?? throw new InvalidOperationException("`DntTreeViewChildrenItem` should be placed inside of a `DntTreeView`."); /// <summary> /// Nested parent item to display /// </summary> [Parameter] public TRecord? ParentItem { set; get; } private IEnumerable<TRecord>? Children => ParentItem is null || SafeOwnerTreeView.CompiledChildrenSelector is null ? null : SafeOwnerTreeView.CompiledChildrenSelector(ParentItem); }
<div class="card" dir="rtl"> <div class="card-header"> DntTreeView </div> <div class="card-body"> <DntTreeView TRecord="Comment" Items="Comments" ChildrenSelector="m => m.Comments" style="list-style: none;" ChildrenHtmlAttributes="ChildrenHtmlAttributes"> <ItemTemplate Context="record"> <div class="card mb-1"> <div class="card-body"> <span>@record.Text</span> </div> </div> </ItemTemplate> <EmptyContentTemplate> <div class="alert alert-warning"> There is no item to display! </div> </EmptyContentTemplate> </DntTreeView> </div> </div>
PdfContentByte content = stamper.GetOverContent(pdfReader.NumberOfPages); Image image = Image.GetInstance(imagePath); image.SetAbsolutePosition(450,650); image.ScaleAbsolute(200,200); content.AddImage(image);
public class Product { public int Id { set; get; } public DateTime AddDate { set; get; } public string Name { set; get; } public decimal Price { set; get; } }
<!DOCTYPE html> <html> <head> <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="~/Content/themes/base/jquery.ui.all.css" rel="stylesheet" /> <link href="~/Content/jquery.jqGrid/ui.jqgrid.css" rel="stylesheet" /> <link href="~/Content/PersianDatePicker.css" rel="stylesheet" /> <link href="~/Content/Site.css" rel="stylesheet" type="text/css" /> </head> <body> <div> @RenderBody() </div> <script src="~/Scripts/jquery-1.7.2.min.js"></script> <script src="~/Scripts/jquery-ui-1.8.11.min.js"></script> <script src="~/Scripts/i18n/grid.locale-fa.js"></script> <script src="~/Scripts/jquery.jqGrid.min.js"></script> <script src="~/Scripts/PersianDatePicker.js"></script> <script src="~/Scripts/jquery.price_format.2.0.js"></script> @RenderSection("Scripts", required: false) </body> </html>
colModel: [ { name: 'Name', index: 'Name', align: 'right', width: 100, editable: true, edittype: 'text', editoptions: { maxlength: 40, dataInit: function (elem) { // http://jqueryui.com/autocomplete/ $(elem).autocomplete({ source: '@Url.Action("GetProductNames","Home")', minLength: 2, select: function (event, ui) { $(elem).val(ui.item.value); $(elem).trigger('change'); } }); } }, editrules: { required: true } } ],
public ActionResult GetProductNames(string term) { var list = ProductDataSource.LatestProducts .Where(x => x.Name.StartsWith(term)) .Select(x => x.Name) .Take(10) .ToArray(); return Json(list, JsonRequestBehavior.AllowGet); }
colModel: [ { name: 'AddDate', index: 'AddDate', align: 'center', width: 100, editable: true, edittype: 'text', editoptions: { maxlength: 10, // https://www.dntips.ir/post/1382 onclick: "PersianDatePicker.Show(this,'@today');" }, editrules: { required: true } } ],
@{ ViewBag.Title = "Index"; var today = DateTime.Now.ToPersianDate(); }
colModel: [ { name: 'Price', index: 'Price', align: 'center', width: 100, formatter: 'currency', formatoptions: { decimalSeparator: '.', thousandsSeparator: ',', decimalPlaces: 2, prefix: '$' }, editable: true, edittype: 'text', editoptions: { dir: 'ltr', dataInit: function (elem) { // http://jquerypriceformat.com/ $(elem).priceFormat({ prefix: '', thousandsSeparator: ',', clearPrefix: true, centsSeparator: '', centsLimit: 0 }); } }, editrules: { required: true, minValue: 0 } } ],
using System; using System.Globalization; using System.Threading; using System.Web.Mvc; namespace jqGrid05.CustomModelBinders { /// <summary> /// How to register it in the Application_Start method of Global.asax.cs /// ModelBinders.Binders.Add(typeof(decimal), new DecimalBinder()); /// </summary> public class DecimalBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (bindingContext.ModelType == typeof(decimal) || bindingContext.ModelType == typeof(decimal?)) { return bindDecimal(bindingContext); } return base.BindModel(controllerContext, bindingContext); } private static object bindDecimal(ModelBindingContext bindingContext) { var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (valueProviderResult == null) return null; bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); decimal value; var valueAsString = valueProviderResult.AttemptedValue == null ? null : valueProviderResult.AttemptedValue.Trim(); if (string.IsNullOrEmpty(valueAsString)) return null; if (!decimal.TryParse(valueAsString, NumberStyles.Any, Thread.CurrentThread.CurrentCulture, out value)) { const string error ="عدد وارد شده معتبر نیست"; var ex = new InvalidOperationException(error, new Exception(error, new FormatException(error))); bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex); return null; } return value; } } }
using System; using System.Web.Mvc; using System.Web.Routing; using jqGrid05.CustomModelBinders; namespace jqGrid05 { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); ModelBinders.Binders.Add(typeof(DateTime), new PersianDateModelBinder()); ModelBinders.Binders.Add(typeof(decimal), new DecimalBinder()); } } }
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="AspNetjQueryAutocompleteTest.Site" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<asp:PlaceHolder Runat="server">
<link href="<%= ResolveClientUrl("~/js/jquery.autocomplete.css")%>" rel="stylesheet" type="text/css" />
</asp:PlaceHolder>
<asp:ContentPlaceHolder ID="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<CompositeScript>
<Scripts>
<asp:ScriptReference Path="~/js/jquery-1.5.min.js" />
<asp:ScriptReference Path="~/js/jquery.autocomplete.js" />
<asp:ScriptReference Path="~/js/custom.js" />
</Scripts>
</CompositeScript>
</asp:ScriptManager>
<div>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
CodeBehind="default.aspx.cs" Inherits="AspNetjQueryAutocompleteTest._default" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<asp:TextBox ID="txtShenas" runat="server" />
</asp:Content>
using System;
using System.Data.SqlClient;
using System.Text;
using System.Web;
namespace AspNetjQueryAutocompleteTest
{
public class AutoComplete : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string prefixText = context.Request.QueryString["q"];
var sb = new StringBuilder();
using (var conn = new SqlConnection())
{
//todo: این مورد باید از فایل کانفیگ خوانده شود
conn.ConnectionString = "Data Source=(local);Initial Catalog=MyDB;Integrated Security = true";
using (var cmd = new SqlCommand())
{
cmd.CommandText = @" select Field1 ,Field2 from tblData where Field1 like @SearchText + '%' ";
cmd.Parameters.AddWithValue("@SearchText", prefixText);
cmd.Connection = conn;
conn.Open();
using (var sdr = cmd.ExecuteReader())
{
if (sdr != null)
while (sdr.Read())
{
string field1 = sdr.GetValue(0) == DBNull.Value ? string.Empty : sdr.GetValue(0).ToString().Trim();
string field2 = sdr.GetValue(1) == DBNull.Value ? string.Empty : sdr.GetValue(1).ToString().Trim();
sb.AppendLine(field1 + "|" + field2);
}
}
}
}
context.Response.Write(sb.ToString());
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
function formatItem(row) {
return row[0] + "<br/><span style='text-align:justify;' dir='rtl'>" + row[1] + "</span>";
}
$(document).ready(function () {
$("#ContentPlaceHolder1_txtShenas").autocomplete('AutoComplete.ashx', {
//Minimum number of characters a user has to type before the autocompleter activates
minChars: 0,
delay: 5,
//Only suggested values are valid
mustMatch: true,
//The number of items in the select box
max: 20,
//Fill the input while still selecting a value
autoFill: false,
//The comparison doesn't looks inside
matchContains: false,
formatItem: formatItem
});
});