<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebAppSingleSubmit._Default" %> <asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent"> <script type="text/javascript"> $(document).ready(function () { $("input[type=submit]").click(function (e) { if ((typeof (Page_ClientValidate) == 'function') && (Page_ClientValidate() == false)) { return false; } if (!confirm("آیا مطمئن هستید؟")) { return false; } this.disabled = true; this.value = 'در حال پردازش اطلاعات ...'; __doPostBack($(this).attr('name'), ''); }); }); </script> </asp:Content> <asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent"> <p> <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox> <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="TextBox1" ErrorMessage="RequiredFieldValidator"></asp:RequiredFieldValidator> <br /> <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" /> </p> </asp:Content>
نحوه صحیح تولید Url در ASP.NET MVC
ممنون از زحماتتون بابت مطالبی که به اشتراک میگذارید
یه سوال از خدمتتون داشتم
من روی یه پروژه MVC کار میکنم. به این صورت کار کردم که یک View کلی دارم که توی اون نمایش اطلاعات و "ایجاد" و "حذف" و "ویرایش" همه یکجا انجام میشن. این View را کاملا با Jquery کار کردم و Insert , Update , Delete کلا توسط Jquery انجام میشه. اما توی یکی از Viewهای دیگه که Strongly Type هم هستش، نمیخام به طور کامل از Jquery استفاده کنم. به این صورت که من اطلاعات جدول مربوطه رو توسط Jquery از پایگاه داده میگیرم و در یک گرید نمایش میدم. توی این گرید برای هر ردیف دوتا لینک "حذف" و "ویرایش" وجود داره. برای حذف هم با Jquery کارمو انجام میدم. اما برای "ایجاد" یک فرم از نوع فرمهای MVC دارم که داخل یه dialog از نوع Jquery قرارشون دادم. برای ایجاد هم مشکلی نیست. اما برای ویرایش، نمیدونم که چطوری باید اطلاعات رو از پایگاه داده لود کنم که خودش مستقیما داخل TextBoxها قرار بگیره. البته توسط Jquery اومدم تک تک textboxها رو به صورت زیر مقدار دادم. حالا نمیدونم کارم درسته یا روش بهتری هست واسه این کار. اگر نیاز هست تا سورس برنامه رو واستون بفرستم
function editmode(val) { $.ajax({ url: "/User/SelectUser", data: { Username: val }, type: "post", dataType: "json", success: function (data) { if (data != "timeout") { if (data.isRedirect) { window.location.href = data.redirectUrl; return; } try { $("#dvEdit").dialog({ modal: 'true', title: 'ویرایش', hide: 'clip' }); //alert($("#dvEdit").html()); $("#UsernameEdit").val(data.Username); $("#FirstNameEdit").val(data.FirstName); $("#LastNameEdit").val(data.LastName); $("#NationalCodeEdit").val(data.NationalCode); $("#EmailEdit").val(data.Email); $("#PhoneNoEdit").val(data.PhoneNo); $("#MobileNoEdit").val(data.MobileNo); $("#CreationDateEdit").val(data.CreationDate); $("#CreationDateEdit").prop('disabled', true); $("#LastActivityDateEdit").val(data.LastActivityDate); $("#LastActivityDateEdit").prop('disabled', true); $("#LastLoginDateEdit").val(data.LastLoginDate); $("#LastLoginDateEdit").prop('disabled', true); $("#IsLockedOutEdit").val(data.IsLockedOut); $("#AddressEdit").val(data.Address); } catch (err) { $("#dverr").show(); $("#lblErr").html(err); } } else AjaxTimeout(); } , error: function (req, textstatus, errorthrown) { AjaxError(req, textstatus, "#dverr", "#lblErr"); } , complete: function (xhr, e) { AjaxComplete(xhr, "#dverr", "#lblErr"); } }); }
namespace TestProject.XamlServices { public class CloseBehavior { public static readonly DependencyProperty CloseProperty = DependencyProperty.RegisterAttached("Close", typeof(bool), typeof(CloseBehavior), new UIPropertyMetadata(false, OnClose)); private static void OnClose(DependencyObject sender, DependencyPropertyChangedEventArgs e) { if (!(e.NewValue is bool) || !((bool) e.NewValue)) return; var win = GetWindow(sender); if (win != null) win.Close(); } private static Window GetWindow(DependencyObject sender) { Window w = null; if (sender is Window) w = (Window)sender; return w ?? (w = Window.GetWindow(sender)); } public static bool GetClose(Window target) { return (bool)target.GetValue(CloseProperty); } public static void SetClose(DependencyObject target, bool value) { target.SetValue(CloseProperty, value); } } }
private bool _isClose; public bool IsClose { get { return _isClose; } set { _isClose = value; OnClosed(); RaisePropertyChanged("IsClose"); } }
<Window x:Class="TestProject.TestView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:xamlServices="clr-namespace:TestProject.XamlServices;assembly=TestProject.XamlServices" xamlServices:CloseBehavior.Close="{Binding IsClose}"> ... </Window>
Extended Events/Trace
Extended Events، زیر ساخت مدیریت رخدادها در SQL Server است. برای مثال در نگارش 2016 آن بیشاز 300 رخداد در SQL Server تعریف شدهاند و زمانیکه در مورد اجرای کوئریها بحث میکنیم، این رخدادها بیشتر مدنظر ما هستند:
sql_statement_completed sp_statement_completed rpc_completed sql_batch_completed
علاوه بر اینها، رخدادهای بسط یافتهی زیر را نیز میتوان مورد استفاده قرار داد:
query_post_compilation_showplan query_post_execution_showplan query_pre_execution_showplan
استفاده از Extended Events برای جمع آوری اطلاعات آماری کوئریها
برای آزمایش نحوهی کار با Extended Events، ابتدا رویهی ذخیره شدهی زیر را ایجاد میکنیم:
USE [WideWorldImporters]; GO DROP PROCEDURE IF EXISTS [Application].[usp_GetCountryInfo]; GO CREATE PROCEDURE [Application].[usp_GetCountryInfo] @Country_Name NVARCHAR(60) AS SELECT * FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = @Country_Name; GO
سپس یک سشن Extended Events سفارشی را به صورت زیر ایجاد میکنیم:
/* Create XE session to capture sql_statement_completed and sp_statement_completed */ IF EXISTS ( SELECT * FROM sys.server_event_sessions WHERE [name] = 'QueryPerf') BEGIN DROP EVENT SESSION [QueryPerf] ON SERVER; END GO CREATE EVENT SESSION [QueryPerf] ON SERVER ADD EVENT sqlserver.sp_statement_completed(WHERE ([duration]>(1000))), ADD EVENT sqlserver.sql_statement_completed(WHERE ([duration]>(1000))) ADD TARGET package0.event_file(SET filename=N'C:\Temp\QueryPerf\test.xel',max_file_size=(256)) WITH ( MAX_MEMORY=16384 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS, MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB, MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=OFF); GO
سپس نیاز است تا این سشن را که QueryPerf نام دارد، در قسمت management->extended events، اجرا و آغاز کرد:
در ادامه ابتدا بر روی بانک اطلاعاتی WideWorldImporters، کلیک راست کرده و یک پنجرهی new query جدید را ایجاد میکنیم:
WHILE 1 = 1 BEGIN EXECUTE [Application].[usp_GetCountryInfo] N'United States'; END
سپس مجددا یک پنجرهی new query دیگر را باز میکنیم:
WHILE 1 = 1 BEGIN SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; END
کوئریهای هر دو پنجره را به صورت مجزایی اجرا کنید. سپس در قسمت management->extended events، بر روی سشن QueryPerf کلیک راست کرده و گزینهی View live data را انتخاب کنید:
این زندهترین خروجی یک سشن رخدادهای بسط یافتهاست. کار کردن با آن نسبت به روشی که در قسمت قبل بررسی کردیم، سادهتر و سریعتر است و همچنین گزارش آن به صورت خودکار تولید میشود.
یک نکته: در اینجا در قسمت Details، اگر بر روی هر ردیف کلیک کنید، امکان انتخاب و نمایش آن در لیست بالای صفحه توسط گزینهی Show Column in table وجود دارد.
در آخر در قسمت management->extended events، بر روی سشن QueryPerf کلیک راست کرده و گزینهی Stop Session را انتخاب کنید. اکنون اگر به پوشهی C:\Temp\QueryPerf مراجعه کنید، فایل xel حاوی اطلاعات این گزارش را نیز میتوانید مشاهده نمائید (به ازای هربار اجرای این سشن، یک فایل جدید را تولید میکند).
این فایل توسط Management Studio قابل گشودن و بررسی است و دقیقا همان نمای گزارش live data را به همراه دارد.
// نصب لودرهای مورد نظر npm install css-loader style-loader -D
//index.html file <html> <head> <title>webpack part 4</title> </head> <body> <h1>webpack is awesome</h1> <p>part 4 of tutorial</p> <div>i have a background</div> <h1>تست فونت !</h1> <script src="/assets/js/bundle.js"> </script> </body> </html>
//webpack.config.js var path = require("path"); var webpack = require("webpack"); module.exports = { context: path.resolve("js"), entry: ['./main.js'] , output: { path: path.resolve("build/js"), publicPath: "assets/js", filename: 'bundle.js' }, devServer: { contentBase: "assets" } , watch: true , module: { loaders: [ { test: /\.css$/ , exclude: /node_modules/ , loader: 'style-loader!css-loader' } ] } }
loader:'style-loader!css-loader'
// main.js file require("./../assets/main.css"); console.log(`i'm bundled by webpack`);
// main.css body{ background-color: #DAA520; }
در تصویر بالا مشخص است که در تگ Head صفحه، یک تگ جدید style، توسط وبپک ایجاد شده و استایل ما به صفحه تزریق شدهاست. همچنین اگر وبپک را به حالت Minify کردن باندل ببریم (در مطلب قبلی نحوهی این کار ذکر شد)، باندل نهایی برای فایلهای css نیز Minify خواهد شد.
استفاده از Sass با کمک وبپک
روش استفاده از Sass نیز تفاوتی با css نخواهد داشت و فقط کافی است Loader آن را در پروژه نصب کنیم و در نهایت آن را در فایل پیکربندی، به وبپک معرفی کنیم. با دستور زیر لودر Sass را در پروژه وارد میکنیم:
// نصب لودر sass npm install -D sass-loader node-sass
( node-sass به عنوان وابستگی لودر sass، در کنار آن نصب شده است)
حال به فایل پیکربندی میرویم و لودر جدید را به قسمت لودرها اضافه میکنیم:
// webpack.config.js module: { loaders: [ { test: /\.css$/ , exclude: /node_modules/ , loader: 'style-loader!css-loader' } ,{ test:/\.scss$/ ,exclude:/node_modules/ ,loader:'style-loader!css-loader!sass-loader' } ] }
در پوشهی assets نیز فایل جدیدی را با عنوان main.scss ساخته و محتوای زیر را در آن وارد میکنیم:
// main.scss $background-color:#DAA520; body{ background-color: $background-color; }
سپس در فایل main.js به جای وارد کردن فایل css قبلی، فایل scss جدید را با کمک require وارد میکنیم و در ادامه وبپک را اجرا میکنیم. خواهیم دید که مانند قبل بدون مشکلی وبپک اجرا شده، فایل scss را به css ترجمه کرده و سپس به کمک بقیه لودرها، به باندل اضافه میکند. استفاده از بقیهی فریمورکهای css مانند Less و ... نیز با کمک لودر آنها به همین صورت قابل انجام است.
استفاده از Autoprefixer
همان طور که تمامی قابلیتهای نسخهی جدید جاوااسکریپت در همهی مرورگرها به صورت سراسری پشتیبانی نمیشود، برای css نیز چنین مشکل مشابهی وجود دارد و برای استفادهی بهینهی از برخی قابلیتها نیاز داریم تا prefixهای مورد نیاز مرورگرهای مختلف را به فایلهای css مان اضافه کنیم. میتوانیم این روند را با کمک یک لودر وبپک، ساده و به صورت خودکار کرد. برای نصب این لودر دستور زیر را وارد میکنیم:
npm install -D autoprefixer-loader
و بعد از نصب شدن آن، در فایل پیکربندی وبپک به لودرهایی که برای فایلهای css و scss اضافه کرده بودیم، این لودر را نیز به صورت زنجیر وار اضافه میکنیم:
//webpack.config.js module: { loaders: [ { test: /\.css$/ , exclude: /node_modules/ , loader: 'style-loader!css-loader!autoprefixer-loader' } ,{ test:/\.scss$/ ,exclude:/node_modules/ ,loader:'style-loader!css-loader!autoprefixer-loader!sass-loader' } ] }
در هر دو لودری که برای css و scss ساخته بودیم، از لودر autoprefixer استفاده کردیم. برای تست اینکه این لودر بدون مشکل کار میکند، در فایل main.scss تغییر زیر را ایجاد میکنیم:
//main.scss $background-color:#DAA520; body{ background-color: $background-color; display: flex; }
حال با اجرای وبپک خواهیم دید که prefixهای مورد نیاز توسط لودر اضافه شده اند ( این لودر از کتابخانهی postcss کمک میگیرد).
باندل کردن تصاویر و فونتها با کمک وبپک
تا اینجا با نحوهی وارد کردن فایلهای استایل، مانند css و ... به باندل آشنا شدیم. در ادامه قصد داریم که تصاویر و فونتها را نیز وارد باندل کنیم. روند کار شبیه به گذشته است و این کار نیز به کمک لودرهای وبپک انجام خواهد شد.
جهت باندل کردن تصاویر و فونتها، به لودر جدیدی با نام url-loader احتیاج داریم. قبل از هر چیزی این لودر را در پروژه با کمک npm نصب میکنیم:
npm install -D url-loader file-loader
(لودر file-loader به عنوان وابستگی مورد نیاز است)
روند همچنان مثل گذشته است و پس از نصب لودر، وارد فایل پیکربندی شده و لودر جدید را به وبپک معرفی میکنیم:
//webpack.config.js file module: { loaders: [ { test: /\.css$/ , exclude: /node_modules/ , loader: 'style-loader!css-loader!autoprefixer-loader' } ,{ test:/\.scss$/ ,exclude:/node_modules/ ,loader:'style-loader!css-loader!autoprefixer-loader!sass-loader' },{ test:/\.(png|jpg|ttf|eot)$/ ,exclude:/node_modules/ ,loader:'url-loader?limit=100000' } ] }
در لودر اضافه شده، پسوند فایلهایی را که قصد داریم به باندل وارد شوند، معرفی میکنیم. در اینجا فرمتهای png , jpg ,ttf, eot ذکر شدهاند.
تنها نکتهی جدید، در مشخص کردن نام لودر وجود دارد و آن نیز قسمت پس از علامت ؟ میباشد. هنگام مشخص کردن اینکه از چه لودری قصد استفاده داریم، میتوانیم با استفاده از ؟ پارامترهایی را به لودر مورد نظر ارسال کنیم. در اینجا به پارامتر limit، مقدار 100000 را دادهایم که برای این لودر به این معناست که اگر حجم فایل در حال پردازش، حجمی بیشتر از این مقدار را داشت، این فایل را به صورت یک لینک جدا از باندل قرار بده. ولی اگر حجمی کمتر از این مقدار داشت، لودر به صورت خودکار فایل را به فرمت Base64 انکود میکند و در درون باندل قرار میدهد.
برای تست اینکه آیا این لودر به درستی کار میکند یا نه، یک تصویر نمونه را در فولدر assets قرار میدهیم و سپس در فایل main.scss تغییرات زیر را انجام میدهیم.
حجم عکس قرار داده شده نزدیک به 400 کیلوبایت است و با مقدار محدودیت مشخص شده، تصویر مورد نظر از باندل توسط وبپک خارج میشود و به صورت جداگانه در بیلد نهایی قرار میگیرد. در تصویر زیر مشخص است که مرورگر درخواست جداگانه ای برای تصویر ارسال کرده است:
حال محدودیت حجم فایل را بالا میبریم و میتوان دید که تصویر در باندل نهایی به صورت انکود شده قرار گرفته است .
قطعا انجام این کار برای تصاویری با حجم بالا مناسب نخواهد بود و برنامه نویس بسته به نیاز بایستی مقدار محدودیت حجم را برای لودر مشخص کند.
در تعریف بالا دیدیم که فرمتهای مورد نیاز برای وارد کردن فونت را نیز علاوه بر تصاویر، برای وبپک مشخص کردهایم. روند وارد کردن فونتها به باندل نیز تفاوتی با تصاویر ندارد و کافی است تعاریف مورد نیاز را در فایلهای css داشته باشیم.
برای مثال فونت ساحل در پوشهی assets قرار داده شده و در فایل main.scss تغییرات زیر انجام شدهاند:
// main.scss $background-color:#DAA520; div{ background-image: url("galaxy.jpg"); } @font-face { font-family: Sahel; src: url('Sahel.eot'); src: url('Sahel.eot?#iefix') format('embedded-opentype'), url('Sahel.woff') format('woff'), url('Sahel.ttf') format('truetype'); font-weight: normal; } @font-face { font-family: Sahel; src: url('Sahel-Bold.eot'); src: url('Sahel-Bold.eot?#iefix') format('embedded-opentype'), url('Sahel-Bold.woff') format('woff'), url('Sahel-Bold.ttf') format('truetype'); font-weight: bold; } @font-face { font-family: Sahel; src: url('Sahel-Black.eot'); src: url('Sahel-Black.eot?#iefix') format('embedded-opentype'), url('Sahel-Black.woff') format('woff'); font-weight: 900; } body{ background-color: $background-color; font-family: 'Sahel'; display: flex; }
تصویر زیر، نتیجهی اجرای وبپک برای تولید باندل است. در تصویر میتوان دید که هم فونتها و هم فایلهای تصاویر، توسط وبپک شناسایی شده و وارد باندل شدهاند:
روش دیگری برای وارد کردن تصاویر نیز موجود است؛ به این صورت که به فرض مثال یک تگ img در اسکریپت ساخته و سپس پروپرتی src آن را با کمک require برابر با آدرس تصویر مورد نظر قرار میدهیم. این روش نیز برای وبپک قابل فهم بوده و فایل وارد باندل میشود. در ادامه مثالی از این روش آورده شده است:
var img = document.createElement("img"); img.width="200px"; img.height="200px"; img.src= require("path to some image");
چند نکتهی پایانی :
1. در فایل پیکربندی همیشه پسوند فایلهایی را که در کلید entry قرار داشتند، مشخص کردیم:
entry:['./main.js','./shared.ts']
با کلیدی با نام resolve در فایل پیکربندی میتوان مشخص کرد در صورتیکه پسوند فایلی مشخص نبود، به ترتیب مشخص شده به دنبال آن بگردد. به طور مثال:
// webpack.config.js resolve:{ extensions:['','.js','.ts'] }
در تعریف بالا ذکر میشود در صورتیکه پسوند فایل ورودی مشخص نبود، ابتدا به دنبال فایل بدون پسوند، سپس فایلهایی با پسوند js و در نهایت به دنبال فایلهایی با پسوند ts بگرد. توجه داشته باشید که ترتیب مشخص کردن پسوند فایلها مهم است و وبپک بر اساس این ترتیب به دنبال فایل مورد نظر خواهد گشت.
حال میتوان مقدار کلید entry را اینطور تعریف کرد:
entry:['./main','./shared']
2.استفاده از فایلهای css ی که در درونشان فونتهای مورد نیاز لینک شدهاند تنها با استفاده از لودر css قابل انجام نیست. به طور مثال استفاده از کتابخانهی بوت استرپ تنها با این لودر ممکن نیست و بایستی لودر url-loader نیز در پروژه نصب شده باشد تا در هنگامیکه وبپک به فونتها برخورد کرد، بتواند آنها را وابسته به شرایط، وارد باندل نهایی کند.
فایلهای پروژه: dntwebpack-part4.zip
با کمک امکانات ارائه شده توسط LINQ ، میتوان بسیاری از اعمال برنامه نویسی را در حجمی کمتر، خواناتر و در نتیجه با قابلیت نگهداری بهتر، انجام داد که تعدادی از آنها را در ادامه مرور خواهیم کرد.
الف) تهیه یک یک رشته، حاوی عناصر یک آرایه، جدا شده با کاما.
using System.Linq;
public class CLinq
{
public static string GetCommaSeparatedListNormal(string[] data)
{
string items = string.Empty;
foreach (var item in data)
{
items += item + ", ";
}
return items.Remove(items.Length - 2, 1).Trim();
}
public static string GetCommaSeparatedList(string[] data)
{
return data.Aggregate((s1, s2) => s1 + ", " + s2);
}
}
ب) پیدا کردن تعداد عناصر یک آرایه حاوی مقداری مشخص
برای مثال آرایه زیر را در نظر بگیرید:
var names = new[] { "name1", "name2", "name3", "name4", "name5", "name6", "name7" };
در تابع GetCountNormal زیر، این کار به شکلی متداول انجام شده و در GetCount از LINQ Count extension method کمک گرفته شده است.
using System.Linq;
public class CLinq
{
public static int GetCountNormal()
{
var names = new[] { "name1", "name2", "name3", "name4", "name5", "name6", "name7" };
var count = 0;
foreach (var name in names)
{
if (name.Contains("name"))
count += 1;
}
return count;
}
public static int GetCount()
{
var names = new[] { "name1", "name2", "name3", "name4", "name5", "name6", "name7" };
return names.Count(name => name.Contains("name"));
}
}
ج) دریافت لیستی از عناصر شروع شده با یک عبارت
در اینجا نیز دو روش متداول و استفاده از LINQ بررسی شده است.
using System.Linq;
using System.Collections.Generic;
public class CLinq
{
public static List<string> GetListNormal()
{
List<string> sampleList = new List<string>() { "A1", "A2", "P1", "P10", "B1", "B@", "J30", "P12" };
List<string> result = new List<string>();
foreach (var item in sampleList)
{
if (item.StartsWith("P"))
result.Add(item);
}
return result;
}
public static List<string> GetList()
{
List<string> sampleList = new List<string>() { "A1", "A2", "P1", "P10", "B1", "B@", "J30", "P12" };
return sampleList.Where(x => x.StartsWith("P")).ToList();
}
}
و در حالت کلی، اکثر حلقههای foreach متداول را میتوان با نمونههای خواناتر کوئریهای LINQ معادل، جایگزین کرد.
در این قسمت میتوانید هر کدام از موارد را که نیاز دارید، نصب کنید. هر کدام از عنوانها در آینده آموزش داده خواهند شد و پیشنهاد میشود آنها را نصب کنید؛ در غیر این صورت فقط گزینهی اول کافی میباشد.
در صورت کلیک بر روی Options، با صفحهی زیر روبرو میشوید که در آن با انتخاب گزینهی Administrative، میتوانید تنظیمات بیشتری را در زمان نصب، انجام دهید و همچنین با انتخاب All users ، نرم افزار برای تمام کاربران سیستم در دسترس خواهد بود.
بعد از اتمام نصب، فایلهای نرم افزار در آدرس زیر در دسترس میباشند:
%LOCALAPPDATA%\JetBrains\Installations
اکنون اگر Visual studio را باز کنید، Resharper به قسمت Extensionsها اضافه شدهاست که در ادامهی آموزش به بررسی آن میپردازیم.
زبانهای پشتیبانی شده
ریشارپر از زبان های C# , VB.NET , TypeScript , JavaScript , C++ , CSS پشتیبانی میکند.
افزایش سرعت Reshaper
یکی از مشکلاتی که بیشتر افراد بعد از نصب این افزونه دارند، مخصوصا اگر سیستم آنها خیلی قوی نباشد، کند شدن Visual Studio میباشد. برای سریعتر کردن این افزونه راه هایی موجود میباشد که به آنها میپردازیم.
- خود ریشارپر پیشنهادهایی را برای بهبود سرعت میکند که آنها در مسیر زیر در دسترس میباشند:
ReSharper | Options | Environment | Performance Guide
- یکی از امکانات ریشارپر، نشان دادن تمام خطاهای موجود در برنامه به صورت یک لیست میباشد که نام این ویژگی، solution-wide analysis است و البته این امکان باعث سنگینی زیاد Visual studio میشود. برای غیر فعال کردن آن میتوانید به مسیر زیر بروید:
ReSharper | Options | Code Inspection | Settings
- راه دیگر انجام اینکار از قسمت تنظیمات خود Visual studio است. برای این کار به مسیر زیر بروید و گزینههای گفته شده را غیر فعال کنید.
Environment | General
Automatically adjust visual experience based on client performance Enable rich client visual experience
- همچنین این گزینه را نیز فعال کنید تا جلوی لگ در UI گرفته شود.
Use hardware graphics acceleration if available
- اگر پروژهی بزرگی را دارید، میتوانید گزینهی زیر را نیز غیر فعال کنید. البته با غیر فعال کردن این گزینه در صورت کرش نرم افزار، کدهای ذخیره نشده ازدست میروند.
Environment | AutoRecover
Save AutoRecover information
- اگر با تعداد فایلهای زیادی کار میکنید، امکان Track changes باعث کندی برنامه میشود. برای غیر فعال کردن این گزینه، به مسیر زیر بروید.
Text Editor | General
Track changes
- خود Visual studio گزینههایی را مانند خطاها در Scroll Bar نشان میدهد که این امکانات در ریشارپر هم موجود میباشد. برای غیر فعال کردن این امکان در Visual Studio برای جلوگیری از دو بار نشان دادن اطلاعات، به مسیر زیر بروید و گزینهی گفته شده را غیر فعال کنید.
Text Editor | All Languages | Scroll Bars
Show annotations over vertical scroll bar
- یکی دیگر از امکانات Visual Studio گزینهای به اسم CodeLens می باشد که یکی از کارهای آن، نشان دادن تمام رفرنسهای توابع یک فایل، در بالای تابع میباشد. این امکان نیز باعث کندی بسیار زیاد برنامه میشود. برای غیر فعال کردن آن میتوانید به مسیر زیر بروید.
Text Editor | All Languages | CodeLens
- هم Visual Studio و هم Reshaper کدهای شما را Format میکنند. پس برای جلوگیری از دوبار انجام شدن این کار، به مسیر زیر بروید و گزینهی گفته شده را غیر فعال کنید.
Text Editor | [Language] | Formatting
auto-formatting
- اگر از تمام امکانات Reshaper نمیخواهید استفاده کنید، میتوانید آنها را از آدرس زیر غیر فعال کنید.
Environment | Products & Features
- اگر در زمان تایپ کردن، برنامه کند میباشد، میتوانید بعضی از امکانات Resharper را از آدرس زیر غیر فعال کنید.
Environment | IntelliSense
Completion Appearance ReSharper's IntelliSense for specific languages
mainReport.Pages[0].Enabled = false; mainReport.Pages["PageName"].Enabled = false;
var mrtName = ""; object businessObject = new object(); if (cusomerReportCondition) { mrtName = "CustomersReport.mrt"; businessObject = getCustomerReportData(); } else if (productsReportCondition) { mrtName = "ProductsReport.mrt"; businessObject = getProductsReportData(); } //else if ... mainReport.Load(mrtName); mainReport.RegBusinessObject("businessObjectName", businessObject);
متدی تحت عنوان ValidateEmail را تصور کنید. این متد از حیث بازگشت نتیجه به عنوان خروجی میتواند به اشکال مختلفی پیاده سازی شود که در ادامه مشاهده میکنیم:
متد ValidateEmail با خروجی Boolean
public bool ValidateEmail(string email) { var valid = true; if (string.IsNullOrWhiteSpace(email)) { valid = false; } var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { valid = false; } var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { valid = false; } return valid; }
همانطور که در تکه کد زیر مشخص میباشد، استفاده کننده از متد بالا، امکان بررسی خروجی آن را در قالب یک شرط خواهد داشت و علاوه بر اینکه پیاده سازی آن ساده میباشد، خوانایی کد را نیز بالا میبرد؛ ولی با این حال نمیتوان متوجه شد مشکل اصلی آدرس ایمیل ارسالی به عنوان آرگومان، دقیقا چیست.
var email = "email@example.com"; var isValid = ValidateEmail(email); if(isValid) { //do something }
متد ValidateEmail با صدور استثناء
public void ValidateEmail(string email) { if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullException(nameof(email)); var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) throw new ArgumentException("email is not in a correct format"); var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) throw new ArgumentException("email does not include a valid domain.") }
روش بالا هم جواب میدهد ولی بهتر است کلاس Exception سفارشی به عنوان مثال ValidationException برای این قضیه در نظر گرفته شود تا بتوان وهلههای صادر شده از این نوع را در لایههای بالاتر مدیریت کرد.
متد ValidateEmail با چندین خروجی
برای این منظور چندین راه حل پیش رو داریم.
با استفاده از پارامتر out:
public bool ValidateEmail(string email, out string message) { var valid = true; message = string.Empty; if (string.IsNullOrWhiteSpace(email)) { valid = false; message = "email is null."; } if (valid) { var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { valid = false; message = "email is not in a correct format"; } } if (valid) { var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { valid = false; message = "email does not include a valid domain."; } } return valid; }
var email = "email@example.com"; var isValid = ValidateEmail(email, out string message); if (isValid) { //do something }
Tuple<bool, List<string>> result = Tuple.Create<bool, List<string>>(true, new List<string>());
public class OperationResult { public bool Success { get; set; } public IList<string> Messages { get; } = new List<string>(); public void AddMessage(string message) { Messages.Add(message); } }
public OperationResult ValidateEmail(string email) { var result = new OperationResult(); if (string.IsNullOrWhiteSpace(email)) { result.Success = false; result.AddMessage("email is null."); } if (result.Success) { var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { result.Success = false; result.AddMessage("email is not in a correct format"); } } if (result.Success) { var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { result.Success = false; result.AddMessage("email does not include a valid domain."); } } return result; }
این بار خروجی متد مذکور از نوع OperationResult ای میباشد که هم موفقیت آمیز بودن یا عدم آن را مشخص میکند و همچنین امکان دسترسی به لیست پیغامهای مرتبط با اعتبارسنجیهای انجام شده، وجود دارد.
استفاده از Exception برای نمایش پیغام برای کاربر نهایی
با صدور یک استثناء و مدیریت سراسری آن در بالاترین (خارجی ترین) لایه و نمایش پیغام مرتبط با آن به کاربر نهایی، میتوان از آن به عنوان ابزاری برای ارسال هر نوع پیغامی به کاربر نهایی استفاده کرد. اگر قوانین تجاری با موفقیت برآورده نشدهاند یا لازم است به هر دلیلی یک پیغام مرتبط با یک اعتبارسنجی تجاری را برای کاربر نمایش دهید، این روش بسیار کارساز میباشد و با یکبار وقت گذاشتن برای توسعه زیرساخت برای این موضوع به عنوان یک Cross Cutting Concern تحت عنوان Exception Management آزادی عمل زیادی در ادامه توسعه سیستم خود خواهید داشت.
به عنوان مثال داشتن یک کلاس Exception سفارشی تحت عنوان UserFriendlyException در این راستا یک الزام میباشد.
[Serializable] public class UserFriendlyException : Exception { public string Details { get; private set; } public int Code { get; set; } public UserFriendlyException() { } public UserFriendlyException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context) { } public UserFriendlyException(string message) : base(message) { } public UserFriendlyException(int code, string message) : this(message) { Code = code; } public UserFriendlyException(string message, string details) : this(message) { Details = details; } public UserFriendlyException(int code, string message, string details) : this(message, details) { Code = code; } public UserFriendlyException(string message, Exception innerException) : base(message, innerException) { } public UserFriendlyException(string message, string details, Exception innerException) : this(message, innerException) { Details = details; } }
و همچنین لازم است در بالاترین لایه سیستم خود به عنوان مثال برای یک پروژه ASP.NET MVC یا ASP.NET Core MVC میتوان یک ExceptionFilter سفارشی نیز تهیه کرد که هم به صورت سراسری استثناءهای سفارشی شما را مدیریت کند و همچنین خروجی مناسب Json برای استفاده در سمت کلاینت را نیز مهیا کند. به عنوان مثال برای درخواستهای Ajax ای لازم است در سمت کلاینت نیز پاسخهای رسیده از سمت سرور به صورت سراسری مدیریت شوند و برای سایر درخواستها همان نمایش صفحات خطای پیغام مرتبط با استثناء رخ داده شده کفایت میکند.
یک مدل پیشنهادی برای تهیه خروجی مناسب برای ارسال جزئیات استثنا رخ داده در درخواستهای Ajax ای
[Serializable] public class MvcAjaxResponse : MvcAjaxResponse<object> { public MvcAjaxResponse() { } public MvcAjaxResponse(bool success) : base(success) { } public MvcAjaxResponse(object result) : base(result) { } public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false) : base(error, unAuthorizedRequest) { } } [Serializable] public class MvcAjaxResponse<TResult> : MvcAjaxResponseBase { public MvcAjaxResponse(TResult result) { Result = result; Success = true; } public MvcAjaxResponse() { Success = true; } public MvcAjaxResponse(bool success) { Success = success; } public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false) { Error = error; UnAuthorizedRequest = unAuthorizedRequest; Success = false; } /// <summary> /// The actual result object of AJAX request. /// It is set if <see cref="MvcAjaxResponseBase.Success" /> is true. /// </summary> public TResult Result { get; set; } } public class MvcAjaxResponseBase { public string TargetUrl { get; set; } public bool Success { get; set; } public ErrorInfo Error { get; set; } public bool UnAuthorizedRequest { get; set; } public bool __mvc { get; } = true; }
[Serializable] public class ErrorInfo { public int Code { get; set; } public string Message { get; set; } public string Detail { get; set; } public Dictionary<string, string> ValidationErrors { get; set; } public ErrorInfo() { } public ErrorInfo(string message) { Message = message; } public ErrorInfo(int code) { Code = code; } public ErrorInfo(int code, string message) : this(message) { Code = code; } public ErrorInfo(string message, string details) : this(message) { Detail = details; } public ErrorInfo(int code, string message, string details) : this(message, details) { Code = code; } }
public async Task CheckIsDeactiveAsync(long id) { if (await _organizationalUnits.AnyAsync(a => a.Id == id && !a.IsActive).ConfigureAwait(false)) throw new UserFriendlyException("واحد سازمانی جاری غیرفعال میباشد."); }
روش نام گذاری متدهایی که امکان بازگشت خروجی Null را دارند
public User GetById(long id);
[Serializable] public class EntityNotFoundException : Exception { public Type EntityType { get; set; } public object Id { get; set; } public EntityNotFoundException() { } public EntityNotFoundException(string message) : base(message) { } public EntityNotFoundException(string message, Exception innerException) : base(message, innerException) { } public EntityNotFoundException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context) { } public EntityNotFoundException(Type entityType, object id) : this(entityType, id, null) { } public EntityNotFoundException(Type entityType, object id, Exception innerException) : base($"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", innerException) { EntityType = entityType; Id = id; } }
یک مثال واقعی
public async Task<UserOrganizationalUnitInfo> GetCurrentOrganizationalUnitInfoOrNullAsync(long userId) { return (await _setting.GetSettingValueForUserAsync( UserSettingNames.CurrentOrganizationalUnitInfo, userId).ConfigureAwait(false)) .FromJsonString<UserOrganizationalUnitInfo>(); }
در ادامه بجای این روشها، از SignalR برای محاسبه تعداد کاربران آنلاین و همچنین به روز رسانی بلادرنگ این عدد در سمت کاربر، استفاده خواهیم کرد.
تشخیص اتصال و قطع اتصال کاربران در SignalR
زیر ساختهای کلاس Hub موجود در SignalR، دارای متدهای ردیابی اتصال (OnConnected)، قطع اتصال (OnDisconnected) و یا برقراری مجدد اتصال کاربران (OnReconnected) هستند. با بازنویسی این متدها میتوان به تخمین بسیار دقیقی از تعداد کاربران آنلاین یک سایت رسید.
پیشنیازهای بحث
پیشنیازهای این بحث با مطلب «مثال - نمایش درصد پیشرفت عملیات توسط SignalR» یکی است. برای مثال نحوه دریافت وابستگیها، تنظیمات فایل global.asax و افزودن اسکریپتها، تفاوتی با مثال یاد شده ندارند.
تعریف هاب کاربران آنلاین برنامه
using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.SignalR; namespace SignalR05.Common { public class OnlineUsersHub : Hub { public static readonly ConcurrentDictionary<string, string> OnlineUsers = new ConcurrentDictionary<string, string>(); public void UpdateUsersOnlineCount() { // آی پی معرف یک کاربر است // اما کانکشن آی دی معرف یک برگه جدید در مرورگر او است // هر کاربر میتواند چندین برگه را به یک سایت گشوده یا ببندد var ipsCount = OnlineUsers.Select(x => x.Value).Distinct().Count(); this.Clients.All.updateUsersOnlineCount(ipsCount); } /// <summary> /// اگر کاربران اعتبار سنجی شدهاند بهتر است از /// this.Context.User.Identity.Name /// بجای آی پی استفاده شود /// </summary> protected string GetUserIpAddress() { object environment; if (!Context.Request.Items.TryGetValue("owin.environment", out environment)) return null; object serverRemoteIpAddress; if (!((IDictionary<string, object>)environment).TryGetValue("server.RemoteIpAddress", out serverRemoteIpAddress)) return null; return serverRemoteIpAddress.ToString(); } public override Task OnConnected() { var ip = GetUserIpAddress(); OnlineUsers.TryAdd(this.Context.ConnectionId, ip); UpdateUsersOnlineCount(); return base.OnConnected(); } public override Task OnReconnected() { var ip = GetUserIpAddress(); OnlineUsers.TryAdd(this.Context.ConnectionId, ip); UpdateUsersOnlineCount(); return base.OnReconnected(); } public override Task OnDisconnected() { // در این حالت ممکن است مرورگر کاملا بسته شده باشد // یا حتی صرفا یک برگه مرورگر از چندین برگه متصل به سایت بسته شده باشند string ip; OnlineUsers.TryRemove(this.Context.ConnectionId, out ip); UpdateUsersOnlineCount(); return base.OnDisconnected(); } } }
در اینجا، هم به IP کاربر و هم به ConnectionId او نیاز است. از این جهت که هر ConnectionId، معرف یک برگه جدید باز شده در مرورگر کاربر است. اگر صرفا IPها را پردازش کنیم، با بسته شدن یکی از چندین برگه مرورگر او که اکنون به سایت متصل هستند، آمار او را از دست خواهیم داد. این کاربر هنوز چندین برگه باز دیگر را دارد که با سایت در ارتباط هستند، اما چون IP او را از لیست حذف کردهایم (در نتیجه بسته شدن یکی از برگهها)، آمار کلی شخص را نیز از دست خواهیم داد. بنابراین هر دوی IP و ConnectionIdها باید پردازش شوند.
اگر برنامه شما دارای اعتبارسنجی است (یک صفحه لاگین دارد)، بهتر است بجای IP از this.Context.User.Identity.Name استفاده کنید.
کدهای سمت کلاینت نمایش آمار کاربران
<html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> <script src="Scripts/jquery-1.6.4.min.js" type="text/javascript"></script> <script src="Scripts/jquery.signalR-1.1.3.min.js" type="text/javascript"></script> <script type="text/javascript" src='<%= ResolveClientUrl("~/signalr/hubs") %>'></script> </head> <body> <form id="form1" runat="server"> online users count: <span id="usersCount"></span> </form> <script type="text/javascript"> $(function () { $.connection.hub.logging = true; var onlineUsersHub = $.connection.onlineUsersHub; onlineUsersHub.client.updateUsersOnlineCount = function (count) { $('#usersCount').text(count); }; $.connection.hub.start(); }); </script> </body> </html>
کدهای کامل این مثال را از اینجا نیز میتوانید دریافت کنید:
SignalR05.zip