As we’ve been moving forward on the Outdated Answers project, one of the things that’s been clear is that on questions with multiple answers, we need a way to surface newer answers that may be more current, while reducing the visibility of older answers that may no longer be correct or relevant
روش های مقایسه اشیاء با null
Check | Code | Description |
Is Null | if(variable is null) return true; |
|
Is Not Null | if(variable is { }) return false |
|
Is Not Null | if(variable is object) return false |
|
Is Null | if(variable == null) return true |
|
Is Not Null | if(variable != null) return false |
|
[Route("api/[controller]")] public class ValuesController : ControllerBase { [HttpGet("{id:int}")] public IActionResult Get(int id) { return Ok(id); } }
[Route("api/[controller]")] public class ValuesController : ControllerBase { [HttpGet("{id}")] public IActionResult Get(int id) { // id is 0 here if you pass string. return Ok(id); } }
1- Inline Constraints
app.UseMvc(routes => { routes.MapRoute("Values", "api/values/{id:int}", new { controller = "Values", action = "Get" }); });
[Route("api/[controller]")] public class ValuesController : ControllerBase { [HttpGet("{name:minlength(2):maxlength(10):alpha}")] public IActionResult Get(string name) { return Ok(name); } }
2- MapRoute's Constraints Argument
app.UseMvc(routes => { routes.MapRoute( name: "Values", template: "api/values/{name}", defaults: new { controller = "Values", action = "Get" }, constraints: new { name = new CompositeRouteConstraint(new List<IRouteConstraint> { new AlphaRouteConstraint(), new MinLengthRouteConstraint(2), new MaxLengthRouteConstraint(10) }) }); });
ایجاد یک Constraint سفارشی
public class StartsWithConstraint : IRouteConstraint { public StartsWithConstraint(string startsWith) { if (string.IsNullOrWhiteSpace(startsWith)) throw new ArgumentNullException(nameof(StartsWith)); StartsWith = startsWith; } private string StartsWith { get; } public bool Match(HttpContext httpContext, IRouter route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { if (parameterName == null) throw new ArgumentNullException(nameof(parameterName)); if (values == null) throw new ArgumentNullException(nameof(values)); if (!values.TryGetValue(parameterName, out var value) || value == null) return false; string valueString = Convert.ToString(value, CultureInfo.InvariantCulture); return valueString.StartsWith(StartsWith); } }
services.Configure<RouteOptions>(opt => opt.ConstraintMap.Add("startsWith", typeof(StartsWithConstraint)));
[Route("api/[controller]")] public class ValuesController : ControllerBase { [HttpGet("{name:minlength(2):maxlength(10):alpha:startsWith(Mo)}")] public IActionResult Get(string name) { return Ok(name); } }
string name = "User 1"; int age = 20; var personTuple = (name, age); Console.WriteLine(personTuple.Item1); // User 1 Console.WriteLine(personTuple.Item2); // 20
string name = "User 1"; int age = 20; var personTuple = (name: name, age:age); Console.WriteLine(personTuple.name); // User 1 Console.WriteLine(personTuple.age); // 20
این وضعیت در C# 7.1 بهبود یافتهاست و اکنون کامپایلر در صورت عدم ذکر صریح نام اعضای Tuple، قادر است این نامها را دقیقا بر اساس نام متغیرها، همانند قابلیتی که در مورد anonymous types وجود دارد، تعیین کند و حدس بزند:
string name = "User 1"; int age = 20; var personTuple = (name, age); Console.WriteLine(personTuple.name); // User 1 Console.WriteLine(personTuple.age); // 20
مثالهایی از حدس زدن نامهای اعضای Tuple در C# 7.1
مثال اول همان حدس زدن نامهای اعضای Tuple بر اساس نام متغیرهای محلی متناظر با آنها است.
مثال دوم بر اساس نام خواص یک شیء است که توسط یک نوع Tuple بازگشت داده میشود:
var p = new Person { Name = "User 1", Age = 20 }; var personTuple = (p.Name, p.Age); Console.WriteLine(personTuple.Name); Console.WriteLine(personTuple.Age);
در اینجا عملگر .? نیز پشتیبانی میشود:
Person p = null; var personTuple = (p?.Name, p?.Age); Console.WriteLine(personTuple.Name); // null Console.WriteLine(personTuple.Age); // null
مثال سوم همان مثال دوم است که در یک عبارت LINQ بکار رفتهاست:
var people = new List<Person> { new Person {Name = "User 1", Age = 42}, new Person {Name = "User 2", Age = 18}, new Person {Name = "User 3", Age = 21} }; var tuples = people .Select(person => ( person.Name, person.Age, NameAndAge: $"{person.Name} is {person.Age}" ) ); var name = tuples.First().Name; var age = tuples.First().Age; var nameAndAge = tuples.First().NameAndAge;
نکته 1: حدس زدن نامها در مورد مقادیر خروجی متدها رخ نمیدهد.
در مثال ذیل نمیتوان به personTuple.FirstName بر اساس نام متد ذکر شده دسترسی یافت و تنها میتوان از Item1 در مورد آن استفاده کرد؛ اما در مورد متغیر محلی age میتوان:
int age = 42; var personTuple = (FirstName(), age); Console.WriteLine(personTuple.Item1); Console.WriteLine(personTuple.age);
نکته 2: اگر نام اعضای Tuple یکی باشند، عملیات حدس زدن نامها رخ نمیدهد.
var p1 = new Person { Name = "User 1", Age = 20 }; var p2 = new Person { Name = "User 2", Age = 22 }; var personTuple = (p1.Name, p2.Name); Console.WriteLine(personTuple.Item1); // User 1 Console.WriteLine(personTuple.Item2); // User 2
پشتیبانی از Full Text Search در SQL Server
Full Text Search یا به اختصار FTS یکی از قابلیتهای SQL Server جهت جستجوی پیشرفته در متون میباشد. این قابلیت تا کنون در EF 6.1.1 ایجاد نشده است.
در ادامه پیاده سازی از FTS در EF را مشاهده مینمایید.
جهت ایجاد قابلیت FTS از متد Contains در Linq استفاده شده است.
ابتدا متدهای الحاقی جهت اعمال دستورات FREETEXT و CONTAINS اضافه میشود.سپس دستورات تولیدی EF را قبل از اجرا بر روی بانک اطلاعاتی توسط امکان Command Interception به دستورات FTS تغییر میدهیم.
همانطور که میدانید دستور Contains در Linq توسط EF به دستور LIKE تبدیل میشود. به جهت اینکه ممکن است بخواهیم از دستور LIKE نیز استفاده کنیم یک پیشوند به مقادیری که میخواهیم به دستورات FTS تبدیل شوند اضافه مینماییم.
جهت استفاده از Fts در EF کلاسهای زیر را ایجاد نمایید.
کلاس FullTextPrefixes :
/// <summary> /// /// </summary> public static class FullTextPrefixes { /// <summary> /// /// </summary> public const string ContainsPrefix = "-CONTAINS-"; /// <summary> /// /// </summary> public const string FreetextPrefix = "-FREETEXT-"; /// <summary> /// /// </summary> /// <param name="searchTerm"></param> /// <returns></returns> public static string Contains(string searchTerm) { return string.Format("({0}{1})", ContainsPrefix, searchTerm); } /// <summary> /// /// </summary> /// <param name="searchTerm"></param> /// <returns></returns> public static string Freetext(string searchTerm) { return string.Format("({0}{1})", FreetextPrefix, searchTerm); } }
کلاس جاری جهت علامت گذاری دستورات FTS میباشد و توسط متدهای الحاقی و کلاس FtsInterceptor استفاده میگردد.
کلاس FullTextSearchExtensions :
public static class FullTextSearchExtensions { /// <summary> /// /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="source"></param> /// <param name="expression"></param> /// <param name="searchTerm"></param> /// <returns></returns> public static IQueryable<TEntity> FreeTextSearch<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) where TEntity : class { return FreeTextSearchImp(source, expression, FullTextPrefixes.Freetext(searchTerm)); } /// <summary> /// /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="source"></param> /// <param name="expression"></param> /// <param name="searchTerm"></param> /// <returns></returns> public static IQueryable<TEntity> ContainsSearch<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) where TEntity : class { return FreeTextSearchImp(source, expression, FullTextPrefixes.Contains(searchTerm)); } /// <summary> /// /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="source"></param> /// <param name="expression"></param> /// <param name="searchTerm"></param> /// <returns></returns> private static IQueryable<TEntity> FreeTextSearchImp<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) { if (String.IsNullOrEmpty(searchTerm)) { return source; } // The below represents the following lamda: // source.Where(x => x.[property].Contains(searchTerm)) //Create expression to represent x.[property].Contains(searchTerm) //var searchTermExpression = Expression.Constant(searchTerm); var searchTermExpression = Expression.Property(Expression.Constant(new { Value = searchTerm }), "Value"); var checkContainsExpression = Expression.Call(expression.Body, typeof(string).GetMethod("Contains"), searchTermExpression); //Join not null and contains expressions var methodCallExpression = Expression.Call(typeof(Queryable), "Where", new[] { source.ElementType }, source.Expression, Expression.Lambda<Func<TEntity, bool>>(checkContainsExpression, expression.Parameters)); return source.Provider.CreateQuery<TEntity>(methodCallExpression); }
در این کلاس متدهای الحاقی جهت اعمال قابلیت Fts ایجاد شده است.
متد FreeTextSearch جهت استفاده از دستور FREETEXT استفاده میشود.در این متد پیشوند -FREETEXT- جهت علامت گذاری این دستور به ابتدای مقدار جستجو اضافه میشود. این متد دارای دو پارامتر میباشد ، اولی ستونی که میخواهیم بر روی آن جستجوی FTS انجام دهیم و دومی عبارت جستجو.
متد ContainsSearch جهت استفاده از دستور CONTAINS استفاده میشود.در این متد پیشوند -CONTAINS- جهت علامت گذاری این دستور به ابتدای مقدار جستجو اضافه میشود. این متد دارای دو پارامتر میباشد ، اولی ستونی که میخواهیم بر روی آن جستجوی FTS انجام دهیم و دومی عبارت جستجو.
متد FreeTextSearchImp جهت ایجاد دستور Contains در Linq میباشد.
کلاس FtsInterceptor :
public class FtsInterceptor : IDbCommandInterceptor { /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { RewriteFullTextQuery(command); } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { RewriteFullTextQuery(command); } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { } /// <summary> /// /// </summary> /// <param name="cmd"></param> public static void RewriteFullTextQuery(DbCommand cmd) { var text = cmd.CommandText; for (var i = 0; i < cmd.Parameters.Count; i++) { var parameter = cmd.Parameters[i]; if ( !parameter.DbType.In(DbType.String, DbType.AnsiString, DbType.StringFixedLength, DbType.AnsiStringFixedLength)) continue; if (parameter.Value == DBNull.Value) continue; var value = (string)parameter.Value; if (value.IndexOf(FullTextPrefixes.ContainsPrefix, StringComparison.Ordinal) >= 0) { parameter.Size = 4096; parameter.DbType = DbType.AnsiStringFixedLength; value = value.Replace(FullTextPrefixes.ContainsPrefix, ""); // remove prefix we added n linq query value = value.Substring(1, value.Length - 2); // remove %% escaping by linq translator from string.Contains to sql LIKE parameter.Value = value; cmd.CommandText = Regex.Replace(text, string.Format( @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", parameter.ParameterName), string.Format(@"CONTAINS([$1].[$2], @{0})", parameter.ParameterName)); if (text == cmd.CommandText) throw new Exception("FTS was not replaced on: " + text); text = cmd.CommandText; } else if (value.IndexOf(FullTextPrefixes.FreetextPrefix, StringComparison.Ordinal) >= 0) { parameter.Size = 4096; parameter.DbType = DbType.AnsiStringFixedLength; value = value.Replace(FullTextPrefixes.FreetextPrefix, ""); // remove prefix we added n linq query value = value.Substring(1, value.Length - 2); // remove %% escaping by linq translator from string.Contains to sql LIKE parameter.Value = value; cmd.CommandText = Regex.Replace(text, string.Format( @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", parameter.ParameterName), string.Format(@"FREETEXT([$1].[$2], @{0})", parameter.ParameterName)); if (text == cmd.CommandText) throw new Exception("FTS was not replaced on: " + text); text = cmd.CommandText; } } } }
در این کلاس دستوراتی را که توسط متدهای الحاقی جهت امکان Fts علامت گذاری شده بودند را یافته و دستور LIKE را با دستورات CONTAINSو FREETEXT جایگزین میکنیم.
در ادامه برنامه ای جهت استفاده از این امکان به همراه دستورات تولیدی آنرا مشاهده مینمایید.
public class Note { /// <summary> /// /// </summary> public int Id { get; set; } /// <summary> /// /// </summary> public string NoteText { get; set; } } public class FtsSampleContext : DbContext { /// <summary> /// /// </summary> public DbSet<Note> Notes { get; set; } /// <summary> /// /// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new NoteMap()); } } /// <summary> /// /// </summary> class Program { /// <summary> /// /// </summary> /// <param name="args"></param> static void Main(string[] args) { DbInterception.Add(new FtsInterceptor()); const string searchTerm = "john"; using (var db = new FtsSampleContext()) { var result1 = db.Notes.FreeTextSearch(a => a.NoteText, searchTerm).ToList(); //SQL Server Profiler result ===>>> //exec sp_executesql N'SELECT // [Extent1].[Id] AS [Id], // [Extent1].[NoteText] AS [NoteText] // FROM [dbo].[Notes] AS [Extent1] // WHERE FREETEXT([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 //char(4096)',@p__linq__0='(john)' var result2 = db.Notes.ContainsSearch(a => a.NoteText, searchTerm).ToList(); //SQL Server Profiler result ===>>> //exec sp_executesql N'SELECT // [Extent1].[Id] AS [Id], // [Extent1].[NoteText] AS [NoteText] // FROM [dbo].[Notes] AS [Extent1] // WHERE CONTAINS([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 //char(4096)',@p__linq__0='(john)' } Console.ReadKey(); } }
ابتدا کلاس FtsInterceptor را به EF معرفی مینماییم. سپس از دو متد الحاقی مذکور استفاده مینماییم. خروجی هر دو متد توسط SQL Server Profiler در زیر هر متد مشاهده مینمایید.
تمامی کدها و مثال مربوطه در آدرس https://effts.codeplex.com قرار گرفته است.
منبع:
Full Text Search in Entity Framework 6
تدارک یک آزمایش برای بررسی میزان افزایش کارآیی متدهای LINQ در دات نت 7
در ادامه یک آزمایش سادهی بررسی کارآیی متدهای Enumerable.Max, Enumerable.Min, Enumerable.Average, Enumerable.Sum را با استفاده از کتابخانهی معروف BenchmarkDotNet مشاهده میکنید:
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System.Collections.Generic; using System.Linq; [MemoryDiagnoser(displayGenColumns: false)] public partial class Program { static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); [Params (10, 10000)] public int Size { get; set; } private IEnumerable<int> items; [GlobalSetup] public void Setup() { items = Enumerable.Range(1, Size).ToArray(); } [Benchmark] public int Min() => items.Min(); [Benchmark] public int Max() => items.Max(); [Benchmark] public double Average() => items.Average(); [Benchmark] public int Sum() => items.Sum(); }
در مورد کار با آرایهها:
- زمان اجرای یافتن Min در آرایههای کوچک، در دات نت 7، نسبت به دات نت 6، حدودا 10 برابر کاهش یافته و اگر این آرایه بزرگتر شود و برای مثال حاوی 10 هزار المان باشد، این زمان 20 برابر کاهش یافتهاست.
- این کاهش زمانها برای سایر متدهای LINQ نیز تقریبا به همین صورت است؛ منها متد Sum که اندازهی آرایه، تاثیری را بر روی نتیجهی نهایی ندارد.
- همچنین در دات نت 7، با فراخوانی متدهای LINQ، افزایش حافظهای مشاهده نمیشود.
در مورد کار با لیستها:
- در دات نت 6، اعمال صورت گرفتهی توسط LINQ بر روی آرایهها، نسبت به لیستها، همواره سریعتر است.
- در دات نت 7 هم در مورد مجموعههای کوچک، وضعیت همانند دات نت 6 است. اما اگر مجموعهها بزرگتر شوند، تفاوتی بین مجموعهها و آرایهها وجود ندارد و حتی وضعیت مجموعهها بهتر است: کارآیی کار با لیستها 32 برابر بیشتر شدهاست!
اما چگونه در دات نت 7، چنین بهبود کارآیی خیرهکنندهای در متدهای LINQ حاصل شدهاست؟
برای بررسی چگونگی بهبود کارآیی متدهای LINQ در دات نت 7 باید به نحوهی پیاده سازی آنها در نگارشهای مختلف دات نت مراجعه کرد. برای مثال پیاده سازی متد الحاقی Min تا دات نت 6 به صورت زیر است:
public static int Min(this IEnumerable<int> source) { if (source == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } int value; using (IEnumerator<int> e = source.GetEnumerator()) { if (!e.MoveNext()) { ThrowHelper.ThrowNoElementsException(); } value = e.Current; while (e.MoveNext()) { int x = e.Current; if (x < value) { value = x; } } } return value; }
اما ... پیاده سازی این متد در دات نت 7 متفاوت است:
public static int Min(this IEnumerable<int> source) => MinInteger(source); private static T MinInteger<T>(this IEnumerable<T> source) where T : struct, IBinaryInteger<T> { T value; if (source.TryGetSpan(out ReadOnlySpan<T> span)) { if (Vector.IsHardwareAccelerated && span.Length >= Vector<T>.Count * 2) { .... // Optimized implementation return ....; } } .... //Implementation as in .NET 6 }
اما ... ReadOnlySpan چیست؟ نوعهای Span و ReadOnlySpan، یک ناحیهی پیوستهی مدیریت شده و مدیریت نشدهی حافظه را بیان میکنند. یک Span از نوع ref struct است؛ یعنی تنها میتواند بر روی stack قرار گیرد که مزیت آن، عدم نیاز به تخصیص حافظهی اضافی و بهبود کارآیی است. همچنین ساختار داخلی Span در سی شارپ 11 اندکی تغییر کردهاست که در آن از ref fields جهت دسترسی امن به این ناحیهی از حافظه استفاده میشود. پیشتر از نوع داخلی ByReference برای اشاره به ابتدای این ناحیهی از حافظه استفاده میشد که به همراه بررسی امنیتی در این باره نبود.
پس از دریافت ReadOnlySpan، به سطر زیر میرسیم:
if (Vector.IsHardwareAccelerated && span.Length >= Vector<T>.Count * 2)
private static T MinInteger<T>(this IEnumerable<T> source) where T : struct, IBinaryInteger<T> { .... if (Vector.IsHardwareAccelerated && span.Length >= Vector<T>.Count * 2) { var mins = new Vector<T>(span); index = Vector<T>.Count; do { mins = Vector.Min(mins, new Vector<T>(span.Slice(index))); index += Vector<T>.Count; } while (index + Vector<T>.Count <= span.Length); value = mins[0]; for (int i = 1; i < Vector<T>.Count; i++) { if (mins[i] < value) { value = mins[i]; } } .... }
تایپوگرافی
هدف از تایپوگرافی، چیدمان متن به نحوی است که واضح، خوانا و مشخص باشد؛ همچنین مباحث زیبایی ارائه را نیز به آن اضافه کنید. برای مثال تنظیم فاصله بین حروف و کلمات، فاصله بین خطوط و یا رعایت یک سری نسبتهای ویژه مانند نسبت طلایی جهت دعوت خواننده به مطالعه مطالب، بجای فراری دادن او، در مباحث تایپوگرافی رعایت میشوند. خوشبختانه Twitter Bootstrap به همراه یک سری تنظیمات تایپوگرافی پیش فرض است که در ادامه آنها را مرور خواهیم کرد.
پیش فرضهای ابتدایی آنرا مانند قلم با اندازه 14px، قلم پیش فرض Helvetica، فاصله بین خطوط و رنگ متن را در فایل bootstrap.css میتوانید مشاهده کنید:
body { margin: 0;"Helvetica Neue", Helvetica, Arial, sans-serif; color: #333333; background-color: #ffffff; }
<div class="row-fluid"> <div class="span12"> <h1> سرتیتر 1 </h1> <h2> سرتیتر 2 </h2> <h3> سرتیتر 3 </h3> <h4> سرتیتر 4 </h4> <h5> سرتیتر 5 </h5> <h6> سرتیتر 6 </h6> </div> </div> |
<div class="row-fluid"> <div class="span12"> <p class="lead"> تیتر</p> <p> متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن </p> <small>اندازه کوچک</small> <strong>متن ضخیم</strong> <em>متن ایتالیک</em> </div> </div>
روش دیگر جلب توجه به یک متن در bootstrap، استفاده از کلاسهای text مانند text-error و امثال آن میباشد:
<div class="row-fluid"> <div class="span12"> <p class="muted"> متن غیرفعال</p> <p class="text-warning"> نمایش اخطار به کاربر</p> <p class="text-error"> نمایش خطا به کاربر</p> <p class="text-info"> نمایش اطلاعات به کاربر</p> <p class="text-success"> نمایش موفقیت آمیز بودن عملیات</p> </div> </div> |
<div class="row-fluid"> <div class="span12"> <p> My <abbr title="منظور واحد پردازش مرکزی است"> CPU</abbr> has N Cores. </p> </div> </div> |
<div class="row-fluid"> <div class="span12"> <address> وحید نصیری <br /> ایران، تهران <br /> <abbr title="Phone"> P:</abbr> 12345678 <br /> <abbr title="Cell"> C:</abbr> 12345678 </address> </div> </div> |
<div class="row-fluid"> <div class="span12"> <blockquote> <p> جهت نمایش نقل قول </p> <small><cite title="کاربر شماره 2">از شخصی</cite> </small> </blockquote> </div> </div> |
<div class="row-fluid"> <div class="span6"> <ul class="unstyled"> <li>یک </li> <li>دو </li> <li>سه </li> </ul> </div> <div class="span6"> <ol> <li>یک </li> <li>دو </li> <li>سه </li> </ol> </div> </div> |
<div class="row-fluid"> <div class="span12"> <dl class="dl-horizontal"> <dt>عنوان</dt> <dd> توضیحات بلند در اینجا</dd> </dl> </div> </div> |
<div class="row-fluid"> <div class="span6"> <code>inline code</code> </div> <div class="span6"> <pre class="pre-scrollable"> code code code </pre> </div> </div> |
استفاده از جداول و تاثیر bootstrap بر آنها
در ادامه کدهای یک جدول متداول را که مزین شدهاست به کلاسهای bootstrap ملاحظه میکنید:
<div class="container-fluid"> <div class="row-fluid"> <div class="span12"> <table class="table table-striped table-hover table-bordered table-condensed"> <caption> عنوانی خاص در اینجا</caption> <thead> <tr> <th> ستون یک </th> <th> ستون دو </th> <th> ستون سه </th> </tr> </thead> <tbody> <tr class="error"> <td> 1 متن یک </td> <td> 1 متن دو </td> <td> 1 متن سه </td> </tr> <tr> <td> 2 متن یک </td> <td> 2 متن دو </td> <td> 2 متن سه </td> </tr> <tr> <td> 3 متن یک </td> <td> 3 متن دو </td> <td> 3 متن سه </td> </tr> </tbody> </table> </div> </div> </div>
در اینجا ذکر caption اختیاری است. وجود thead و tbody به تشخیص هدر و ردیفها جهت اعمال شیوهنامههای متناظر کمک میکنند. همچنین کلاسهای ذیل نیز به جدول اعمال شدهاند:
table: سبب میشود تا تنظیمات ابتدایی bootstrap به جدول طراحی شده، اعمال شوند.
table-striped: رنگ زمینه سطرها را یک در میان تغییر میدهد.
table-hover: سبب میشود تا با عبور اشارهگر ماوس از روی سطرها، رنگ زمینه آنها تغییر کنند.
table-bordered: حاشیهای را به جدول و ردیفها اعمال میکنند. همچنین سبب نمایش گوشههای گرد نیز میشود.
table-condensed: اندکی padding اعمال شده به سلولهای جداول را کاهش میدهد و جدول را فشردهتر میکند.
در جدول فوق، کلاس نمونه error به یک tr نیز اعمال شدهاست تا اثر آنرا بر روی یک ردیف بهتر بتوان ملاحظه کرد.
طراحی فرمها و تاثیر bootstrap بر آنها
قرار دادن برچسبها و عناصر صفحه، به نحوی کاربرپسند و دلپذیر، نیاز به رعایت یک سری اصول تایپوگرافی و طراحی ویژه دارد که این موارد نیز در bootstrap گنجانده شدهاند.
1) فرمهای عمودی
<div class="container-fluid"> <div class="row-fluid"> <div class="span12"> <form action="/signup" method="post"> <fieldset> <legend>ثبت نام</legend> <label> ایمیل:</label> <input name="email" type="text" /> <label> نام:</label> <input name="name" type="text" /> </fieldset> </form> </div> </div> </div> |
همانطور که ملاحظه میکنید، کلیه عناصر را به صورت یک پشته عمودی در صفحه قرار دادهاست و نکته مهم اینجا است که هیچگونه شیوه نامه خاص و اضافهتری به فرم فوق از طرف ما اعمال نشده است.
نکته: اگر میخواهید همان حالت پیش فرض مرورگر، یعنی قرار دادن تمام عناصر در پشت سر هم را فعال کنید، تنها کافی است کلاس form-inline را به form تعریف شده فوق اضافه نمائید.
2) نکاتی در مورد checkboxes و radio buttons
در حالت پیش فرض، با تعریف یک label و checkbox، برچسب متناظر با آن اندکی بالاتر از checkbox قرار گرفته شده در صفحه ظاهر میشود. برای رفع این مشکل تنها کافی است کلاس checkbox به label اعمال شود (و برای radio button از کلاس radio استفاده خواهد شد):
<label class="checkbox"> <input type="checkbox" name="isMale" /> مذکر</label>
نکته: برای قرار دادن چندین checkbox یا radio button در یک سطر (با توجه به حالت چیدمان عمودی پیش فرض فرمها)، ابتدا آنها را داخل یک div قرار دهید. سپس به تمام checkboxها یا radio buttonها کلاس inline را نیز اضافه نمائید. برای مثال:
<div> <label class="radio inline"> <input type="radio" name="isMale" /> مذکر</label> <label class="radio inline"> <input type="radio" name="isFemale" /> مؤنث</label> </div> |
4) تعیین اندازه فیلدها
اگر علاقمند هستید که یک textarea کل عرض صفحه را به خود اختصاص دهد، از کلاس input-block-level استفاده کنید.
5) فرمهای جستجو
<form class="search-form"> <fieldset> <legend>جستجو</legend> <input type="search" class="search-query" /> <button type="submit" class="btn" >بیاب</button> </fieldset> </form> |
- کلاس فرم بهتر است search-form تعیین شود
- نوع input بهتر است search وارد شود
- کلاس input جستجو نیز search-query انتخاب گردد
همانطور که ملاحظه میکنید، در این حالت گوشههای جعبه متنی جستجو، نسبت به حالتهای معمولی آنها گرد شده است. کلاس دکمه نیز btn درنظر گرفته شده است تا حالت ویژه دکمههای bootstrap را پیدا کند.
6) فرمهای افقی
تا اینجا، با فرمهای حالت پیش فرض یا فرمهایی که عناصر را به صورت پشتهای عمودی بر روی یکدیگر قرار میدهند، آشنا شدیم. حالت متداول دیگر طراحی فرمها، حالت افقی است. به این معنا که در هر سطر، یک برچسب و یک المان قرار گیرند، بجای اینکه ابتدا برچسب نمایش داده شود و در سطر بعدی، المان مرتبط با آن. Bootstrap برای طراحی بدون استفاده از جداول این نوع فرمها نیز تنظیمات خاصی را تدارک دیدهاست.
<form class="form-horizontal" action="/signup" method="post"> <fieldset> <legend>ثبت نام</legend> <div class="control-group"> <label class="control-label"> ایمیل:</label> <div class="controls"> <input name="email" type="text" /> </div> </div> <div class="control-group"> <label class="control-label"> نام:</label> <div class="controls"> <input name="name" type="text" /> </div> </div> <div class="control-group"> <div class="controls"> <label class="radio inline"> <input type="radio" name="isMale" /> مذکر</label> <label class="radio inline"> <input type="radio" name="isFemale" /> مؤنث</label> </div> </div> </fieldset> </form> |
- کلاس form-horizontal را به فرم جاری اضافه کنید.
- هر سطر مورد نظر را در div ایی با کلاس control-group محصور نمائید.
- به برچسبها، کلاس control-label را انتساب دهید.
- کنترلهای مدنظر را در div ایی با کلاس controls محصور کنید.
هر چند این روش نیاز به اندکی HTML نویسی دارد، اما بسیاری به این نوع فرمها بیشتر علاقمند هستند تا فرمهای عمودی ابتدای بحث.
7) جلب توجه کاربران به فیلدها برای نمایش خطاهای اعتبارسنجی
<div class="control-group error"> <label class="control-label"> ایمیل:</label> <div class="controls"> <input name="email" type="text" /> <span class="help-block">لطفا ایمیل را با فرمت صحیحی وارد نمائید</span> </div> </div> |
8) توسعه فیلدهای استاندارد
<div class="input-prepend input-append"> <span dir="ltr" class="add-on">.00</span> <input dir="ltr" type="text" /> <span class="add-on">ریال</span> </div> |
9) HTML Helpers مخصوص ASP.NET MVC برای کار با bootstrap
نکاتی را که در اینجا مطرح شدند، اگر علاقمند بودید که به شکلی strongly typed در ASP.NET MVC اعمال کنید، میتوان به پروژههایی مانند TwitterBootstrapMvc مراجعه کرد. تعداد این نوع پروژهها هم روز به روز بیشتر میشوند:
https://twitterbootstrapmvc.codeplex.com/
https://mvc4bootstaphelper.codeplex.com/
https://github.com/erichexter/twitter.bootstrap.mvc
http://bootstraphelpers.codeplex.com/
تنظیمات خاص دکمهها در حین استفاده از Twitter Bootstrap
در مثال ذیل، کلاسهای مرتبط با تزئین دکمهها را توسط bootstrap، ملاحظه میکنید:
<div class="container-fluid"> <div class="row-fluid"> <div class="span12"> <table class="table table-striped table-hover table-bordered table-condensed"> <thead> <tr> <th> دکمه </th> <th> لینک </th> <th> کلاس بکار گرفته شده </th> </tr> </thead> <tbody> <tr> <td> <button class="btn"> default</button> </td> <td> <a href="#" class="btn">default</a> </td> <td> <code>btn</code> </td> </tr> <tr> <td> <button class="btn btn-primary"> primary</button> </td> <td> <a href="#" class="btn btn-primary">primary</a> </td> <td> <code>btn btn-primary</code> </td> </tr> <tr> <td> <button class="btn btn-info"> info</button> </td> <td> <a href="#" class="btn btn-info">info</a> </td> <td> <code>btn btn-info</code> </td> </tr> <tr> <td> <button class="btn btn-success"> success</button> </td> <td> <a href="#" class="btn btn-success">success</a> </td> <td> <code>btn btn-success</code> </td> </tr> <tr> <td> <button class="btn btn-warning"> warning</button> </td> <td> <a href="#" class="btn btn-warning">warning</a> </td> <td> <code>btn btn-warning</code> </td> </tr> <tr> <td> <button class="btn btn-danger"> danger</button> </td> <td> <a href="#" class="btn btn-danger">danger</a> </td> <td> <code>btn btn-danger</code> </td> </tr> <tr> <td> <button class="btn btn-inverse"> inverse</button> </td> <td> <a href="#" class="btn btn-inverse">inverse</a> </td> <td> <code>btn btn-inverse</code> </td> </tr> <tr> <td> <button class="btn btn-link"> link</button> </td> <td> <a href="#" class="btn btn-link">link</a> </td> <td> <code>btn btn-link</code> </td> </tr> <tr> <td> <button class="btn btn-primary btn-large"> large</button> </td> <td> <a href="#" class="btn btn-primary btn-large">large</a> </td> <td> <code>btn btn-primary btn-large</code> </td> </tr> <tr> <td> <button class="btn btn-primary btn-small"> small</button> </td> <td> <a href="#" class="btn btn-primary btn-small">small</a> </td> <td> <code>btn btn-primary btn-small</code> </td> </tr> <tr> <td> <button class="btn btn-primary btn-mini"> mini</button> </td> <td> <a href="#" class="btn btn-primary btn-mini">mini</a> </td> <td> <code>btn btn-primary btn-mini</code> </td> </tr> <tr> <td> <button class="btn btn-primary btn-block"> block</button> </td> <td> <a href="#" class="btn btn-primary btn-block">block</a> </td> <td> <code>btn btn-primary btn-block</code> </td> </tr> <tr> <td> <button class="btn btn-primary disabled"> disabled</button> </td> <td> <a href="#" class="btn btn-primary disabled">disabled</a> </td> <td> <code>btn btn-primary disabled</code> </td> </tr> </tbody> </table> </div> </div> </div>
همانطور که ملاحظه کردید، الزامی ندارد که این کلاسها را حتما به دکمهها اعمال کرد. برای نمونه میتوان از یک span یا لینک نیز برای تعریف دکمهها بهره جست. برای یکپارچه سازی چنین دکمههایی (که در اصل دکمه نیستند) با ASP.NET MVC میتوان به مطلب تکمیلی «استفاده از دکمههای CSS توئیتر در ASP.NET MVC» مراجعه نمود.
یک نکته: اگر علاقمند هستید که تعدادی دکمه را به شکل یک toolbar نمایش دهید، آنها را در یک div محصور کرده و کلاس btn-group را به آن div اعمال نمائید.
کار با تصاویر و آیکونها در Twitter Bootstrap
کلاسهایی مانند img-rounded، img-circle، img-polaroid با اعمال به یک تصویر، سبب گردن شدن گوشههای آن، نمایش دایرهای و یا نمایش به همراه حاشیه یک تصویر خواهند شد.
Twitter Bootstrap به همراه صدها آیکون ارائه شده است. این آیکونها توسط glyphicons.com ایجاد شدهاند. روشی که برای استفاده از آنها توصیه شده است، استفاده از تگ i میباشد. برای مثال:
<i class="icon-music"></i>icon-music
برای مشاهده لیست کلاسهای قابل استفاده، کلمه icon-glass را در فایل bootstrap.css جستجو نمائید؛ تا شروع مدخل مرتبط با آیکونها را بتوانید مشاهده نمائید.
رنگ پیش فرض این آیکونها مشکی است. اگر علاقمند بودید که آنها را برای مثال با رنگ سفید نمایش دهید فقط کافی است کلاس icon-white را پس از کلاس آیکون مدنظر،ذکر کرد:
<i class="icon-music icon-white"></i>icon-music
امکان اعمال این آیکونها به دکمهها نیز وجود دارد. برای مثال:
<button class="btn"> <i class="icon-music"></i>دکمه</button>
<div class="input-prepend"> <span class="add-on"><i class="icon-envelope"></i></span> <input type="email" dir="ltr" /> </div> |
Strong Name
System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35
برای استفاده از این تکنولوژی ابتدا نیاز است تا یک جفتکلید عمومی/خصوصی (توسط ادمین، منبع گواهینامهها، یک بانک یا یک ابزار خاص) فراهم شود تا از آن برای اینکریپشن استفاده شود. سپس دادههای موردنظر (هر داده کلی که قصد ارسال و توزیع آن را داریم مثل یک اسمبلی) با استفاده از یک الگوریتم هشکردن (مثل MD5، SHA یا ترکیبی از آنها، هرچند MD5 توصیه نمیشود) پردازش شده و یک هشکد مخصوص تولید میشود. این هشکد با استفاده از کلید خصوصی دردسترس اینکریپت میشود و به عنوان یک امضای دیجیتال به همراه داده موردنظر ارسال یا توزیع میشود. در سمت مصرف کننده که با استفاده از یک روش خاص و امن به کلید عمومی دسترسی پیدا کرده است عملیات دیکریپت کردن این امضای دیجیتال با استفاده از کلید عمومی انجام شده و هشکد مربوطه بدست میآید. همچنین عملیات تولید هشکد با استفاده از دادهها در سمت مصرف کننده انجام شده و هشکد دادهها نیز دوباره با استفاده از همان الگوریتم استفاده شده در سمت توزیعکننده تولید میشود. سپس این دو مقدار محاسبه شده در سمت مصرفکننده با یکدیگر مقایسه شده و درصورت برابر بودن میتوان اطمینان حاصل کرد همان دادهای که توزیع کننده در اصل ارسال کرده بدون تغییر به دست مصرف کننده رسیده است. درواقع ویژگی اینکریپت/دیکریپت کردن دادهها توسط جفتکلید این است که بهصورت یکطرفه بوده و دادههای اینکریپت شده با استفاده از یک کلید خصوصی را تنها با استفاده از کلید عمومی همان کلید خصوصی میتوان بدرستی دیکریپت کرد.
1. تولید و مدیریت جفتکلیدهای قوی- نامگذاریشده (Strongly Named Key Pairs)
همانطور که در قسمت قبل اشاره شد برای نامگذاری قوی یک اسمبلی به یک کلید عمومی (public key) و یک کلید خصوصی (private key) که در مجموع به آن یک جفت کلید (key pair) میگویند، نیاز است.برای اینکار میتوان با استفاده از برنامه sn.exe (عنوان کامل آن Microsoft .Net Framework Strong Name Utility است) یک جفت کلید تولید کرده و آن را در یک فایل و یا در CSP (یا همان cryptographic service provider) ذخیره کرد. همچنین اینکار را میتوان توسط ویژوال استودیو نیز انجام داد. امکان موردنظر در فرم پراپرتی یک پروژه و در تب Signing آن وجود دارد.
نکته: یک CSP عنصری از API کریپتوگرافی ویندوز (Win32 CryptoAPI) است که سرویسهایی چون اینکریپشن، دیکریپشن، و تولید امضای دیجیتال را فراهم میکند. این پرووایدرها همچنین تسهیلاتی برای مخازن کلیدها فراهم میکنند که از اینکریپشنهای قوی و ساختار امنیتی سیستم عامل (سیستم امنیتی و دسترسی کاربران ویندوز) برای محافظت از تمام کلیدهای کریپتوگرافی ذخیره شده در مخزن استفاده میکند. بهطور خلاصه و مفید میشود اشاره کرد که میتوان کلیدهای کریپتوگرافی را درون یک مخزن کلید CSP ذخیره کرد و تقریبا مطمئن بود که تا زمانیکه هیچکس کلمه عبور سیستم عامل را نداند، این کلیدها امن خواهند ماند. برای کسب اطلاعات بیشتر به دادههای CryptoAPI در اسناد SDK سیستم عامل خود مراجعه کنید.
برنامه sn به همراه SDKهای ویندوز نصب میشود. البته با نصب ویژوال استودیو تمام SDKهای موردنیاز مطابق با نسخههای موجود، نصب خواهد شد. مسیر نسخه 4 و 32 بیتی این برنامه در سیستم عامل Windows 7 بهصورت زیر است:
C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\sn.exe
با استفاده از آرگومان k همانند دستور زیر یک جفتکلید جدید تولید شده و در فایل MyKeys.snk در ریشه درایو d: ذخیره میشود:
sn –k d:\MyKeys.snk
نکته: به بزرگی و کوچکی حروف سوییچهای دستورات برنامه sn دقت کنید!
این کار یک جفت کلید کریپتوگرافی 1024 بیتی بهصورت تصادفی تولید میکند. این دستور را باید در خط فرمانی (Command Prompt) اجرا نمود که مسیر فایل sn.exe را بداند. برای راحتی کار میتوان از خط فرمان ویژوال استودیو (Visual Studio Command Prompt) استفاده کرد.
نکته: اجرای عملیات فوق در یک شرکت یا قسمت توسعه یک شرکت، تنها یک بار نیاز است زیرا تمام اسمبلیهای تولیدی تا زمانیکه عناوین ساده متمایزی دارند میتوانند از یک جفت کلید مشترک استفاده کنند.
نکته: هرچند که میتوان از پسوندهای دیگری نیز برای نام فایل حاوی جفت کلید استفاده کرد، اما توصیه میشود از همین پسوند snk. استفاده شود.
فایل تولید شده حاوی هر دو کلید «عمومی» و «خصوصی» است. میتوان با استفاده از دستور زیر کلید عمومی موجود در فایل mykeys.snk را استخراج کرده و در فایل mypublickey.snk ذخیره کرد:
sn –p d:\mykeys.snk d:\mypublickey.snk
sn -tp MyPublicKey.snk
sn -i MyKeys.snk MyStrongNameKeys
sn –m n
sn –m y
sn -d MyStrongNameKeys
نکته: برای استفاده از این ویژگی در ویژوال استودیو، باید در تب Signing در تنظیمات پروژه گزینه Sign the Assembly را انتخاب کرد. سپس میتوان فایل حاوی جفت کلیدهای تولیدشده را انتخاب یا فایل جدیدی تولید کرد. البته ویژوال استودیو تا نسخه 2010 امکانی جهت استفاده از مخازن CSP را ندارد.
[assembly:AssemblyKeyFileAttribute("MyKeys.snk")]
sn –vf MyAsm.exe
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. Failed to verify assembly -- Strong name validation failed for assembly MyAsm.exe'.
sn –p d:\MyKeys.snk d:\MyPublicKey.snk sn –pc MyKeysContainer d:\MyPublicKey.snk
csc.exe /delaysign /keyfile:d:\MyPublicKey.snk /out:d:\MyAsm.exe d:\Class1.cs
al /out:<assembly name> <module name> /keyfile:<file name>
sn –Vr d:\MyAsm.exe
sn –R d:\MyAsm.exe MyKeys.snk sn –R d:\MyAsm.exe MyKeysContainer
sn –D assembly1 assembly2
sn –Vu d:\MyAsm.exe
sn –Vx
sn –Vl
C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe
gacutil /i c:\MyAsm.dll
gacutil /u MyAsm
gacutil /u MyAsm,Version=1.3.0.5
gacutil /l
gacutil /l MyAsm