Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Metric","time":"2019-01-08T11:23:01.0000000Z","tags":{"ai.cloud.roleInstance":"BAHARI-PC","ai.internal.sdkVersion":"m-agg2:2.8.1-22898"},"data":{"baseType":"MetricData","baseData":{"ver":2,"metrics":[{"name":"Dependency duration","kind":"Aggregation","value":4143.777,"count":1,"min":4143.777,"max":4143.777,"stdDev":0}],"properties":{"_MS.MetricId":"dependencies/duration","_MS.IsAutocollected":"True","_MS.AggregationIntervalMs":"60000","Dependency.Type":"Http","DeveloperMode":"true","Dependency.Success":"True"}}}} Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Metric","time":"2019-01-08T11:23:01.0000000Z","tags":{"ai.cloud.roleInstance":"BAHARI-PC","ai.internal.sdkVersion":"m-agg2:2.8.1-22898"},"data":{"baseType":"MetricData","baseData":{"ver":2,"metrics":[{"name":"Server response time","kind":"Aggregation","value":4136.7653,"count":1,"min":4136.7653,"max":4136.7653,"stdDev":0}],"properties":{"_MS.MetricId":"requests/duration","_MS.IsAutocollected":"True","_MS.AggregationIntervalMs":"60000","DeveloperMode":"true","Request.Success":"True"}}}} Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Metric","time":"2019-01-08T11:23:01.0000000Z","tags":{"ai.cloud.roleInstance":"BAHARI-PC","ai.internal.sdkVersion":"m-agg2:2.8.1-22898"},"data":{"baseType":"MetricData","baseData":{"ver":2,"metrics":[{"name":"Dependency duration","kind":"Aggregation","value":3972.6929,"count":3,"min":12.8297,"max":2687.0447,"stdDev":1092.34881420812}],"properties":{"_MS.MetricId":"dependencies/duration","_MS.IsAutocollected":"True","_MS.AggregationIntervalMs":"60000","Dependency.Type":"SQL","DeveloperMode":"true","Dependency.Success":"True"}}}}
#region Using using Autofac; using CHK.ServiceLayer.Interfaces; using DNTScheduler; using System; #endregion namespace CHK.Web.Scheduler { public class InventoryTask : ScheduledTaskTemplate { #region Properties public override int Order => 1; public override string Name => "Inventory-DiscountCoupon-GiftCard"; public IContainer Container { get; set; } #endregion #region Methods public override bool RunAt(DateTime utcNow) { if (IsShuttingDown || Pause) return false; var currentDateTime = utcNow.AddHours(3.5); return (currentDateTime.Minute % 15 == 0 && currentDateTime.Second > 5 && currentDateTime.Second < 15); } public override void Run() { if (IsShuttingDown || Pause) return; Pause = true; using (var scope = Container.BeginLifetimeScope()) { var schedulerService = scope.Resolve<ISchedulerService>(); schedulerService.UpdateInventory(); } Pause = false; } #endregion } }
صفحات مودال در بوت استرپ 3
- استفاده از modal dialogs مجموعه Twitter Bootstrap برای گرفتن تائید از کاربر
- نمایش فرمهای مودال Ajax ایی در ASP.NET MVC به کمک Twitter Bootstrap
این کدها نیاز به اندکی تغییر دارند تا با سیستم بوت استرپ 3 سازگار شوند.
ارتقاء کدهای صفحات مودال بوت استرپ 2 به 3
- اگر پیشتر به کلاس modal، کلاس hide را نیز اضافه میکردید، اکنون دیگر نیازی نیست؛ زیرا hide بودن به صورت پیش فرض اعمال میشود (بودن آن هم سبب میشود تا یک صفحه خاکستری نمایش داده شود؛ اما از صفحه مودال خبری نباشد).
- کلاسهای modal-header، modal-body و modal-footer بوت استرپ 2، باید داخل یک div با کلاس modal-content محصور شوند.
- کلاس modal-content باید داخل کلاس modal-dialog محصور شود.
یک مثال:
<div class="container"> <h4 class="alert alert-info"> فرمهای مودال بوت استرپ 3</h4> <div class="row"> <a data-toggle="modal" href="#myModal" class="btn btn-primary">نمایش صفحه مودال</a> <div class="modal" id="myModal"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"> ×</button> <h4 class="modal-title"> عنوان</h4> </div> <div class="modal-body"> محتوای صفحه در اینجا </div> <div class="modal-footer"> <a href="#" data-dismiss="modal" class="btn">بستن</a> <a href="#" class="btn btn-primary"> ذخیره سازی تغییرات</a> </div> </div> </div> </div> </div> <!-- end row --> </div> <!-- /container -->
در این مثال، سلسله مراتب کلاسهای modal ایی که باید تعریف شوند را ملاحظه میکنید. همچنین لینکی با ویژگی data-toggle مساوی modal سبب نمایش این قسمت مخفی از صفحه، به صورت مودال خواهد شد.
در مثالهایی که با بوت استرپ 2 مشاهده کردید (در مقدمه بحث جاری)، این محتوای مخفی به صورت پویا با جاوا اسکریپت به body صفحه اضافه میشود.
بارگذاری یک صفحه مودال Ajax ایی
در بوت استرپ سه میتوان با استفاده از خاصیت remote تنظیمات نمایش یک صفحه مودال، به صورت خودکار اینگونه صفحات را بارگذاری کرد:
$('#myModal').modal({ show: true, remote: '/myNestedContent' });
<a data-toggle="modal" class="btn btn-primary" href="@renderModalPartialViewUrl" data-target="#myModal">Click me</a> <div class="modal fade" id="myModal" tabindex="-1" role="dialog"></div>
نکته مهم: در حالت ریموت، طراحی محتوایی که باید نمایش داده شود، نباید شامل سطر ذیل باشد. در غیراینصورت اطلاعاتی نمایش داده نخواهد شد:
<div class="modal" id="myModal">
به روز رسانی مثالهای ASP.NET MVC جهت سازگاری با بوت استرپ 3
مثال فوق را به همراه کدهای اصلاح شده دو مثال ابتدای بحث (jquery.bootstrap-modal-ajax-form.js و jquery.bootstrap-modal-confirm.js)، از لینک ذیل میتوانید دریافت کنید. این مثال به همراه قالب t4 افزودن Viewهای مودال بوت استرپ (CreateBootstrap3ModalForm.tt) نیز هست.
bs3-sample06.zip
dotnet new -i FeatherHttp.Templates::0.1.67-alpha.g69b43bed72 --nuget-source https://f.feedz.io/featherhttp/framework/nuget/index.json
Templates Short Name Language Tags ---------------------------------------------------------------------------------------------------------------------------------- FeatherHttp feather [C#] Web/ASP.NET/FeatherHttp
dotnet new feather --name todoAPI
همانطور که مشاهده میکنید پروژهی فوق تنها شامل دو فایل .csproj و Program.cs است. درون Program.cs و متد Main کار initialize کردن سرور HTTP صورت گرفته است. WebApplication.Create دقیقا همانند Host.CreateDefaultBuilder پروژههای ASP.NET Core عمل میکند؛ یعنی پیکربندی pipeline از قبیل اضافه کردن متغیرهای محیطی، خواندن از فایل JSON و ... را انجام میدهد اما با کد boilerplate کمتر. بنابراین خروجی WebApplication.Create یک ASP.NET Core Pipeline با قابلیت اضافه کردن تنظیمات دلخواه است. در ادامه جهت بررسی بیشتر Feather HTTP، یک مدل را به همراه یک سری دیتای In-memory به پروژه اضافه خواهیم کرد:
using System.Collections.Generic; using System.Text.Json.Serialization; using System.Linq; namespace todoAPI.Models { public class Todo { [JsonPropertyName("id")] public int Id { get; set; } [JsonPropertyName("title")] public string Title { get; set; } [JsonPropertyName("completed")] public bool Completed { get; set; } } public class TodoData { private readonly IList<Todo> _db = new List<Todo> { new Todo { Id = 1, Title = "Read book" }, new Todo { Id = 2, Title = "Watch an episode of Dark" }, new Todo { Id = 3, Title = "Publish a post on dotnettips" }, new Todo { Id = 4, Title = "Skype with my friend" }, }; public IList<Todo> GetAllToDoItmes() { return _db; } public void AddTodo(Todo item) { _db.Add(item); } public void ToggleTodo(int id) { var todo = _db.FirstOrDefault(x => x.Id == id); todo.Completed = !todo.Completed; } public void DeleteTodo(int id) { var todo = _db.FirstOrDefault(x => x.Id == id); _db.Remove(todo); } } }
در مثال فوق برای نگاشت نام خواص، از System.Text.Json توکار NET Core 3.0. استفاده شدهاست. در ادامه نیز از یک کلاس برای شبیهسازی CRUD یک Todo استفاده شدهاست. سپس برای داشتن اندپوینتهای موردنظر به ازای هر کدام از متدهای فوق درون متد Main، از app.Map... استفاده کردهایم:
using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using todoAPI.Models; namespace todoAPI { class Program { private static readonly TodoData db = new TodoData(); static async Task Main(string[] args) { var app = WebApplication.Create(args); app.MapGet("/", GetTodos); app.MapPost("/api/todos", CreateTodo); app.MapPost("/api/todos/{id}", ToggleTodo); app.MapDelete("/api/todos/{id}", DeleteTodo); await app.RunAsync(); } static async Task GetTodos(HttpContext http) { var todos = db.GetAllToDoItmes(); await http.Response.WriteJsonAsync(todos); } static async Task CreateTodo(HttpContext http) { var todo = await http.Request.ReadJsonAsync<Todo>(); db.AddTodo(todo); http.Response.StatusCode = 204; } static async Task ToggleTodo(HttpContext http) { if (!http.Request.RouteValues.TryGet("id", out int id)) { http.Response.StatusCode = 400; return; } db.ToggleTodo(id); http.Response.StatusCode = 204; } static async Task DeleteTodo(HttpContext http) { if (!http.Request.RouteValues.TryGet("id", out int id)) { http.Response.StatusCode = 400; return; } db.DeleteTodo(id); http.Response.StatusCode = 204; } } }
هر کدام از اندپوینتهای فوق، یک ورودی HttpContext دریافت خواهند کرد. توسط این شیء میتوانیم به درخواست جاری و همچنین به پاسخ درخواست، دسترسی داشته باشیم.
استفاده از سیستم DI توکار NET Core.
همانطور که در ابتدای مطلب نیز عنوان شد، Feather HTTP یک wrapper بر روی APIهای موجود ASP.NET Core است، بنابراین میتوانیم از همان سرویس DI که درون پروژههای ASP.NET Core در اختیار داریم در اینجا نیز استفاده کنیم. در ادامه یک پوشهی جدید را به مثال قبل، با نام Controllers اضافه خواهیم کرد و درون آن یک فایل TodoController را با محتویات زیر ایجاد خواهیم کرد:
using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using todoAPI.Models; using todoAPI.Services; namespace todoAPI.Controllers { public class TodoController { private readonly ITodoService _todoService; public TodoController(ITodoService todoService) { _todoService = todoService; } public async Task GetTodos(HttpContext http) { var todos = _todoService.GetAllToDoItmes(); await http.Response.WriteJsonAsync(todos); } public async Task CreateTodo(HttpContext http) { var todo = await http.Request.ReadJsonAsync<Todo>(); _todoService.AddTodo(todo); http.Response.StatusCode = 204; } public async Task ToggleTodo(HttpContext http) { if (!http.Request.RouteValues.TryGet("id", out int id)) { http.Response.StatusCode = 400; return; } _todoService.ToggleTodo(id); http.Response.StatusCode = 204; } public async Task DeleteTodo(HttpContext http) { if (!http.Request.RouteValues.TryGet("id", out int id)) { http.Response.StatusCode = 400; return; } _todoService.DeleteTodo(id); http.Response.StatusCode = 204; } } }
کاری که انجام شده است، انتقال تمامی متدهای static به کلاس فوق و سپس جایگزین کردن کلمهی کلیدی static با public است. همچنین یه ارجاع به اینترفیس جدید با عنوان ITodoService اضافه شده است؛ درون پیادهسازی این اینترفیس همان متدهای کلاس TodoData را اضافه کردهایم:
using System.Collections.Generic; using todoAPI.Models; using System.Linq; namespace todoAPI.Services { public interface ITodoService { void AddTodo(Todo item); void DeleteTodo(int id); IList<Todo> GetAllToDoItmes(); void ToggleTodo(int id); } public class TodoService : ITodoService { private readonly IList<Todo> _db = new List<Todo> { new Todo { Id = 1, Title = "Read book" }, new Todo { Id = 2, Title = "Watch an episode of Dark" }, new Todo { Id = 3, Title = "Publish a post on dotnettips" }, new Todo { Id = 4, Title = "Skype with my friend" }, }; public IList<Todo> GetAllToDoItmes() { return _db; } public void AddTodo(Todo item) { _db.Add(item); } public void ToggleTodo(int id) { var todo = _db.FirstOrDefault(x => x.Id == id); todo.Completed = !todo.Completed; } public void DeleteTodo(int id) { var todo = _db.FirstOrDefault(x => x.Id == id); _db.Remove(todo); } } }
نکته: برای ایجاد اینترفیس از روی یک کلاس درون VS Code میتوانیم اینگونه عمل کنیم:
تغییرات فایل Program.cs
ابتدا باید using مربوط به DI را در ابتدای فایل اضافه کنیم:
using Microsoft.Extensions.DependencyInjection;
سپس توسط ServiceProvider یک وهله از کلاس موردنظر را ایجاد کردهایم و همچنین سرویسهای موردنظر را درون DI Container اضافه کردهایم:
using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using todoAPI.Controllers; using todoAPI.Services; namespace todoAPI { class Program { static async Task Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddTransient<TodoController>(); builder.Services.AddTransient<ITodoService, TodoService>(); var serviceProvider = builder.Services.BuildServiceProvider(); var todoController = serviceProvider.GetService<TodoController>(); var app = WebApplication.Create(args); app.MapGet("/", todoController.GetTodos); app.MapPost("/api/todos", todoController.CreateTodo); app.MapPost("/api/todos/{id}", todoController.ToggleTodo); app.MapDelete("/api/todos/{id}", todoController.DeleteTodo); await app.RunAsync(); } } }
Convention Over Configuration
در کد قبلی به صورت دستی TodoController را توسط Service Location از DI درخواست کردهایم. اینکار را در ادامه میتوانیم به Feather HTTP سپرده تا کار وهلهسازی را براساس قواعد توکار برایمان انجام دهد:
using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using todoAPI.Services; namespace todoAPI { class Program { static async Task Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); builder.Services.AddControllers(); builder.Services.AddSingleton<ITodoService, TodoService>(); var serviceProvider = builder.Services.BuildServiceProvider(); var app = builder.Build(); app.MapControllers(); await app.RunAsync(); } } }
سپس در ادامه برای دسترسی به HTTP Context درون TodoController از IHttpContextAccessor استفاده کردهایم:
using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using todoAPI.Models; using todoAPI.Services; namespace todoAPI.Controllers { public class TodoController { private readonly ITodoService _todoService; private readonly IHttpContextAccessor _accessor; public TodoController(ITodoService todoService, IHttpContextAccessor accessor) { _todoService = todoService; _accessor = accessor; } [HttpGet("/todos")] public async Task GetTodos() { var todos = _todoService.GetAllToDoItmes(); await _accessor.HttpContext.Response.WriteJsonAsync(todos); } [HttpPost("/todos")] public async Task CreateTodo() { var todo = await _accessor.HttpContext.Request.ReadJsonAsync<Todo>(); _todoService.AddTodo(todo); _accessor.HttpContext.Response.StatusCode = 204; } [HttpPost("/todos/{id}")] public async Task ToggleTodo(int id) { _todoService.ToggleTodo(id); _accessor.HttpContext.Response.StatusCode = 204; } [HttpDelete("/todos/{id}")] public async Task DeleteTodo(int id) { _todoService.DeleteTodo(id); _accessor.HttpContext.Response.StatusCode = 204; } } }
کدهای کامل مطلب را میتوانید از اینجا دریافت کنید.
در اینجا برای بررسی مقدماتی کامپوننتها، یک پروژهی جدید React را ایجاد میکنیم.
> create-react-app sample-04 > cd sample-04 > npm start
> npm install --save bootstrap
پس از اجرای این دستور، ممکن است پیامهای اخطاری مانند «requires a peer of jquery@1.9.1 - 3 but none is installed» را نیز مشاهده کنید که مهم نیستند. چون در اینجا صرفا از امکانات CSS ای بوت استرپ استفاده خواهیم کرد و کاری با jQuery نداریم. محل نصب آن نیز پوشهی node_modules\bootstrap برنامه است.
سپس برای افزودن فایل bootstrap.css به پروژهی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
ایجاد اولین کامپوننت React
در پوشهی src برنامه، پوشهی جدیدی را به نام components ایجاد میکنیم و تمام کامپوننتهای خود را در آن قرار خواهیم داد. سپس داخل این پوشه، یک فایل جدید و خالی را به نام counter.jsx ایجاد میکنیم. پسوند این فایل jsx است و نام فایلهای کامپوننتها را نیز camel case وارد میکنیم؛ یعنی اولین حرف اولین واژهی وارد شده، با حروف کوچک و تمام واژههای پس از آن با حروف بزرگ شروع خواهند شد مانند coolApp. مزیت استفادهی از پسوند jsx نسبت به js، فراهم شدن امکانات مخصوص React در VSCode است.
در ابتدای فایل counter.jsx، نیاز است وابستگیهای React را import کنیم. اگر از قسمت اول بخاطر داشته باشید، «simple react snippets» را نیز در VSCode نصب کردیم. به کمک آن میتواند این نوع importها را سادهتر وارد کرد. برای این منظور imrc را تایپ کرده و سپس دکمهی tab را فشار دهید. به این ترتیب یک سطر زیر به صورت خودکار تولید میشود:
import React, { Component } from 'react';
import React, { Component } from "react"; class Counter extends Component { render() { return <h1>Hello world!</h1>; } } export default Counter;
خروجی متد render در اینجا، یک رشتهی معمولی نیست؛ بلکه یک عبارت jsx است که در قسمت اول معرفی شد. این عبارت در نهایت توسط کامپایلر Babel به معادل React.createElement ترجمه میشود. به همین جهت نیاز است تا import React را در ابتدای این ماژول درج کرد؛ هرچند به ظاهر به صورت مستقیم از آن استفاده نمیکنیم.
تا اینجا این کامپوننت در UI برنامه نمایش داده نمیشود. به همین جهت به فایل index.js مراجعه کرده و آنرا به صورت زیر تغییر میدهیم:
- ابتدا نیاز است تا شیء Counter را در اینجا import کنیم و چون خروجی پیشفرض است، نیازی به ذکر {} برای معرفی آن نیست:
import Counter from "./components/counter";
ReactDOM.render(<Counter />, document.getElementById("root"));
درج چند عنصر در عبارات JSX
میخواهیم در کامپوننت Counter، یک دکمه را نیز نمایش دهیم. برای انجام اینکار، به نحو زیر عمل میکنیم:
render() { return <h1>Hello world!</h1><button>Increment</button>; }
عبارات JSX در نهایت باید تبدیل به متد React.createElement شوند. اولین پارامتر این متد، نوع المانی است که قرار است ایجاد شود که در اینجا h1 است. اما در اینجا دو المان را داریم. در این حالت Babel نمیداند که چگونه باید یک چنین عبارتی را به React.createElement ترجمه کند. یک راه حل این است که کل این عبارت را داخل یک div قرار داد:
render() { return ( <div> <h1>Hello world!</h1> <button>Increment</button> </div> );
return ; <div></div>
return ( <div></div> );
return ( <React.Fragment> <h1>Hello world!</h1> <button>Increment</button> </React.Fragment> );
نکته 2: در VSCode برای ویرایش همزمان ابتدا و انتهای یک تگ (برای مثال ویرایش همزمان عبارت div در اینجا و تبدیل آن به React.Fragment در دو قسمت)، عبارت آن تگ را انتخاب کرده و سپس دکمههای ctrl+d را فشار دهید تا بتوانید همزمان هر دو عبارت انتخاب شده را با هم ویرایش کنید. به اینکار multi-cursor editing میگویند.
نمایش پویای اطلاعات در عبارات JSX
در ادامه بجای نمایش عبارت ثابت «Hello world»، میخواهیم آنرا به صورت پویا تنظیم کنیم. برای این منظور یک خاصیت جدید را در کلاس جاری، به نام state تعریف کرده و آنرا با یک شیء، مقدار دهی میکنیم. state، یک خاصیت ویژه در کامپوننتهای React است و بیانگر دادههایی است که آن کامپوننت نیاز دارد. این دادهها میتوانند یک key/value ساده باشند و یا حتی value تعریف شده نیز میتواند یک شیء پیچیده باشد.
import React, { Component } from "react"; class Counter extends Component { state = { count: 0 }; render() { return ( <React.Fragment> <span>{this.state.count}</span> <button>Increment</button> </React.Fragment> ); } } export default Counter;
همانطور که عنوان شد در بین {}ها میتوان هر نوع عبارت مجاز جاوا اسکریپتی را ذکر کرد و عبارت چیزی است که مقداری را بازگشت میدهد. بنابراین عبارتی مانند {2+2} را نیز میتوان در اینجا بکار برد و یا حتی در اینجا میتوان متدی را فراخوانی کرد که مقداری را بازگشت میدهد:
import React, { Component } from "react"; class Counter extends Component { state = { count: 0 }; render() { return ( <React.Fragment> <span>{this.formatCount()}</span> <button>Increment</button> </React.Fragment> ); } formatCount() { const { count } = this.state; // Object Destructuring return count === 0 ? "Zero" : count; } } export default Counter;
در متد formatCount حتی میتوان عبارات JSX را نیز بجای یک رشتهی ساده، بازگشت داد:
formatCount() { const { count } = this.state; // Object Destructuring return count === 0 ? <h1>Zero</h1> : count; }
مقدار دهی ویژگیهای عناصر در عبارات JSX
فرض کنید یک المان img را به عبارت JSX کلاس Counter اضافه کردهایم. اکنون میخواهیم ویژگی src آنرا مقدار دهی کنیم. در اینجا هر چیزی که بین "" قرار گیرد، به صورت یک رشتهی ثابت پردازش میشود. برای تنظیم آن به یک متغیر، ابتدا خاصیت state را به صورت زیر جهت درج imageUrl، ویرایش میکنیم:
state = { count: 0, imageUrl: "/logo192.png" };
render() { return ( <React.Fragment> <img src={this.state.imageUrl} alt="" /> <span>{this.formatCount()}</span> <button>Increment</button> </React.Fragment> ); }
return ( <React.Fragment> <img src={this.state.imageUrl} alt="" /> <span className="badge badge-primary m-2">{this.formatCount()}</span> <button className="btn btn-secondary btn-sm">Increment</button> </React.Fragment> );
تا اینجا اگر فایل کامپوننت Counter را ذخیره کنید، خروجی ذیل در مرورگر ظاهر خواهد شد:
روش مقدار دهی ویژگی style نیز متفاوت است. در اینجا React انتظار دارد تا شیءای را که به صورت زیر تشکیل میشود:
styles = { fontSize: 50, fontWeight: "bold" };
return ( <React.Fragment> <img src={this.state.imageUrl} alt="" /> <span style={this.styles} className="badge badge-primary m-2"> {this.formatCount()} </span> <button className="btn btn-secondary btn-sm">Increment</button> </React.Fragment> );
اعمال این styles نمونه، یک چنین خروجی را به همراه خواهد داشت:
مزیت تعریف شیء styles به صورت یک خاصیت در کلاس، امکان استفادهی مجدد از آن در سایر المانها است. اگر چنین چیزی مدنظر شما نیست، میتوان این شیء را به صورت inline هم تعریف کرد:
<button style={{ fontSize: 30 }} className="btn btn-secondary btn-sm">
مقدار دهی پویای ویژگی className عناصر در عبارات JSX
در ادامه میخواهیم اگر مقدار count مساوی صفر بود، span ای که هم اکنون با یک badge آبی (با کلاس badge-primary) نمایش داده میشود، زرد رنگ (با کلاس badge-warning) شود و در غیراینصورت آبی رنگ. بنابراین میخواهیم بر اساس مقدر count، مقدار کلاسهای انتسابی به className را به صورت پویا تغییر دهیم و این الگویی است که در برنامههای واقعی بسیار استفاده میشود:
render() { let classes = "badge m-2 badge-"; classes += this.state.count === 0 ? "warning" : "primary"; return ( <React.Fragment> <img src={this.state.imageUrl} alt="" /> <span style={this.styles} className={classes}> {this.formatCount()} </span> <button style={{ fontSize: 30 }} className="btn btn-secondary btn-sm"> Increment </button> </React.Fragment> );
البته باید دقت داشت که میتوان منطق تشکیل متغیر classes را به یک متد، جهت خلوت سازی متد render نیز منتقل کرد. برای این کار، دو سطر مرتبط با متغیر classes را در VSCode انتخاب کنید. سپس یک آیکن لامپ مانند ظاهر میشود که با کلیک بر روی آن، منوی extract to method نیز قابل انتخاب است:
render() { let classes = this.getBadgeClasses(); // ... } getBadgeClasses() { let classes = "badge m-2 badge-"; classes += this.state.count === 0 ? "warning" : "primary"; return classes; }
<span style={this.styles} className={this.getBadgeClasses()}>
کلیدهای مربوط به Request
ضروری؟ | نام کلید | مقدار |
بله | "owin.RequestBody" | یک Stream همراه با request body. اگر body برای request وجود نداشته باشد، Stream.Null به عنوان placeholder قابل استفاده است. |
بله | "owin.RequestHeaders" | یک دیکشنری به صورت IDictionary<string, string[]> از هدرهای درخواست. |
بله | "owin.RequestMethod" | رشتهایی حاوی نوع فعل متد HTTP مربوط به درخواست (مانند GET and POST ) |
بله | "owin.RequestPath" | path درخواست شده به صورت string |
بله | "owin.RequestPathBase" | قسمتی از path درخواست به صورت string |
بله | "owin.RequestProtocol" | نام و نسخهی پروتکل (مانند HTTP/1.0 or HTTP/1.1 ) |
بله | "owin.RequestQueryString" | رشتهای حاوی query string ؛ بدون علامت ? (مانند foo=bar&baz=quux ) |
بله | "owin.RequestScheme" | رشتهایی حاوی URL scheme استفاده شده در درخواست (مانند HTTP or HTTPS ) |
ضروری؟ | نام کلید | مقدار |
بله | "owin.ResponseBody" | یک Stream جهت نوشتن response body در خروجی |
بله | "owin.ResponseHeaders" | یک دیکشنری به صورت IDictionary<string, string[]> از هدرهای response |
خیر | "owin.ResponseStatusCode" | یک عدد صحیح حاوی کد وضعیت HTTP response ؛ حالت پیشفرض 200 است. |
خیر | "owin.ResponseReasonPhrase" | یک رشته حاوی reason phrase مربوط به status code ؛ اگر خالی باشد در نتیجه سرور بهتر است آن را مقداردهی کند. |
خیر | "owin.ResponseProtocol" | یک رشته حاوی نام و نسخهی پروتکل (مانند HTTP/1.0 or HTTP/1.1 )؛ اگر خالی باشد؛ “owin.RequestProtocol” به عنوان مقدار پیشفرض در نظر گرفته خواهد شد. |
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net461" /> <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net461" /> <package id="Owin" version="1.0" targetFramework="net461" />
using Owin; namespace SimpleOwinWebApp { public class Startup { public void Configuration(IAppBuilder app) { } } }
using Owin; namespace SimpleOwinWebApp { public class Startup { public void Configuration(IAppBuilder app) { app.Use(async (ctx, next) => { await ctx.Response.WriteAsync("Hello"); }); } }
Func<IOwinContext, Func<Task>, Task> handler
app.Use(async (ctx, next) => { var response = ctx.Environment["owin.ResponseBody"] as Stream; using (var writer = new StreamWriter(response)) { await writer.WriteAsync("Hello"); } });
using System; using Microsoft.Owin.Hosting; namespace SimpleOwinConsoleApp { class Program { static void Main(string[] args) { using (WebApp.Start<Startup>("http://localhost:12345")) { Console.WriteLine("Listening to port 12345"); Console.WriteLine("Press Enter to end..."); Console.ReadLine(); } } } }
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace SimpleOwinCoreApp { public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } } }
using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace SimpleOwinCoreApp.Middlewares { public class SimpleMiddleware { private readonly RequestDelegate _next; public SimpleMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext ctx) { // قبل از فراخوانی میانافزار بعدی await ctx.Response.WriteAsync("Hello DNT!"); await _next(ctx); // بعد از فراخوانی میانافزار بعدی } } }
app.UseMiddleware<SimpleMiddleware>();
"Microsoft.AspNetCore.Owin": "1.0.0"
app.UseOwin(pipeline => { pipeline(next => new MyKatanaBasedMiddleware(next).Invoke) });
using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace SimpleOwinAspNetCore.Middleware { public class IpBlockerMiddleware { private readonly RequestDelegate _next; private readonly IpBlockerOptions _options; public IpBlockerMiddleware(RequestDelegate next, IpBlockerOptions options) { _next = next; _options = options; } public async Task Invoke(HttpContext context) { var ipAddress = context.Request.Host.Host; if (IsBlockedIpAddress(ipAddress)) { context.Response.StatusCode = 403; await context.Response.WriteAsync("Forbidden : The server understood the request, but It is refusing to fulfill it."); return; } await _next.Invoke(context); } private bool IsBlockedIpAddress(string ipAddress) { return _options.Ips.Any(ip => ip == ipAddress); } } }
using System.Collections.Generic; namespace SimpleOwinAspNetCore.Middleware { public class IpBlockerOptions { public IpBlockerOptions() { Ips = new[] { "192.168.1.1" }; } public IList<string> Ips { get; set; } } }
using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; namespace SimpleOwinAspNetCore.Middleware { public static class IpBlockerExtensions { public static IApplicationBuilder UseIpBlocker(this IApplicationBuilder builder, IConfigurationRoot configuration, IpBlockerOptions options = null) { return builder.UseMiddleware<IpBlockerMiddleware>(options ?? new IpBlockerOptions { Ips = configuration.GetSection("block_list").GetChildren().Select(p => p.Value).ToArray() }); } } }
{ "block_list": [ "192.168.1.1", "localhost", "127.0.0.1", "172.16.132.151" ] }
public IConfigurationRoot Configuration { set; get; } public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("blockedIps.json"); Configuration = builder.Build(); }
app.UseIpBlocker(Configuration);
Blazor.addEventListener('enhancedload', () => { // ... });
// @ts-ignore
{ "compilerOptions": { "strict": true, "removeComments": true, "sourceMap": false, "noEmitOnError": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true, "target": "ES2020", "outFile": "wwwroot/scripts/app.js" }, "include": [ "Scripts/**/*.ts" ], "exclude": [ "node_modules" ] }
<base href="/" /> <!-- for local host -->
<head> <link rel="stylesheet" href="./style.css" ></link> </head>
<head> <link rel="stylesheet" href="/style.css" ></link> </head>
- کلیک راست بر روی تصویر
- انتخاب چندین تصویر
- بهینهسازی تصاویر موجود در فایلهای CSS
ایجاد تصاویر Sprite
<?xml version="1.0" encoding="utf-8"?> <sprite xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://vswebessentials.com/schemas/v1/sprite.xsd"> <settings> <!--Determines if the sprite image should be automatically optimized after creation/update.--> <optimize>true</optimize> <!--Determines the orientation of images to form this sprite. The value must be vertical or horizontal.--> <orientation>vertical</orientation> <!--File extension of sprite image.--> <outputType>png</outputType> <!--Determin whether to generate/re-generate this sprite on building the solution.--> <runOnBuild>false</runOnBuild> <!--Use full path to generate unique class or mixin name in CSS, LESS and SASS files. Consider disabling this if you want class names to be filename only.--> <fullPathForIdentifierName>true</fullPathForIdentifierName> <!--Use absolute path in the generated CSS-like files. By default, the URLs are relative to sprite image file (and the location of CSS, LESS and SCSS).--> <useAbsoluteUrl>false</useAbsoluteUrl> <!--Specifies a custom subfolder to save CSS files to. By default, compiled output will be placed in the same folder and nested under the original file.--> <outputDirectoryForCss /> <!--Specifies a custom subfolder to save LESS files to. By default, compiled output will be placed in the same folder and nested under the original file.--> <outputDirectoryForLess /> <!--Specifies a custom subfolder to save SCSS files to. By default, compiled output will be placed in the same folder and nested under the original file.--> <outputDirectoryForScss /> </settings> <!--The order of the <file> elements determines the order of the images in the sprite.--> <files> <file>/Content/Images/01.png</file> <file>/Content/Images/02.png</file> <file>/Content/Images/03.png</file> <file>/Content/Images/04.png</file> </files> </sprite>
/* This is an example of how to use the image sprite in your own CSS files */ .Content-Images-01 { /* You may have to set 'display: block' */ width: 32px; height: 32px; background: url('icons.png') 0 0; } .Content-Images-02 { /* You may have to set 'display: block' */ width: 32px; height: 32px; background: url('icons.png') 0 -32px; } .Content-Images-03 { /* You may have to set 'display: block' */ width: 32px; height: 32px; background: url('icons.png') 0 -64px; } .Content-Images-04 { /* You may have to set 'display: block' */ width: 32px; height: 32px; background: url('icons.png') 0 -96px; }
<div class="Content-Images-01"></div> <div class="Content-Images-02"></div> <div class="Content-Images-03"></div> <div class="Content-Images-04"></div>
<img src="https://www.dntips.ir/images/logo.png" />
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC" />
<img src="data:image/png;base64,iVBOR..." />