پیش نیازها
- شروع یک پروژهی جدید وب با پشتیبانی از Web API
- نصب دو بستهی نیوگت مرتبط با Structure map 3
PM>install-package structuremap PM>install-package structuremap.web
پیاده سازی IHttpControllerActivator توسط Structure map
using System; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using StructureMap; namespace WebApiDISample.Core { public class StructureMapHttpControllerActivator : IHttpControllerActivator { private readonly IContainer _container; public StructureMapHttpControllerActivator(IContainer container) { _container = container; } public IHttpController Create( HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { var nestedContainer = _container.GetNestedContainer(); request.RegisterForDispose(nestedContainer); return (IHttpController)nestedContainer.GetInstance(controllerType); } } }
نکتهی مهم آن استفاده از NestedContainer آن است. معرفی آن به متد request.RegisterForDispose سبب میشود تا کلیه کلاسهای IDisposable نیز در پایان کار به صورت خودکار رها سازی شده و نشتی حافظه رخ ندهد.
معرفی StructureMapHttpControllerActivator به برنامه
فایل WebApiConfig.cs را گشوده و تغییرات ذیل را در آن اعمال کنید:
using System.Web.Http; using System.Web.Http.Dispatcher; using StructureMap; using WebApiDISample.Core; using WebApiDISample.Services; namespace WebApiDISample { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // IoC Config ObjectFactory.Configure(c => c.For<IEmailsService>().Use<EmailsService>()); var container = ObjectFactory.Container; GlobalConfiguration.Configuration.Services.Replace( typeof(IHttpControllerActivator), new StructureMapHttpControllerActivator(container)); // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }
تهیه سرویسی برای آزمایش برنامه
namespace WebApiDISample.Services { public interface IEmailsService { void SendEmail(); } } using System; namespace WebApiDISample.Services { /// <summary> /// سرویسی که دارای قسمت دیسپوز نیز هست /// </summary> public class EmailsService : IEmailsService, IDisposable { private bool _disposed; ~EmailsService() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public void SendEmail() { //todo: send email! } protected virtual void Dispose(bool disposeManagedResources) { if (_disposed) return; if (!disposeManagedResources) return; //todo: clean up resources here ... _disposed = true; } } }
نکتهی مهم آن استفاده از IDisposable در این کلاس خاص است (ضروری نیست؛ صرفا جهت بررسی بیشتر اضافه شدهاست). اگر در کدهای برنامه، یک چنین کلاسی وجود داشت، نیاز است متد Dispose آن نیز توسط IoC Container فراخوانی شود. برای آزمایش آن یک break point را در داخل متد Dispose قرار دهید.
استفاده از سرویس تعریف شده در یک Web API Controller
using System.Web.Http; using WebApiDISample.Services; namespace WebApiDISample.Controllers { public class ValuesController : ApiController { private readonly IEmailsService _emailsService; public ValuesController(IEmailsService emailsService) { _emailsService = emailsService; } // GET api/values/5 public string Get(int id) { _emailsService.SendEmail(); return "_emailsService.SendEmail(); called!"; } } }
تزریق وهلهی مورد نیاز آن، به صورت خودکار توسط StructureMapHttpControllerActivator که در ابتدای بحث معرفی شد، صورت میگیرد.
فراخوانی متد Get آنرا نیز توسط کدهای سمت کاربر ذیل انجام خواهیم داد:
<h2>Index</h2> @section scripts { <script type="text/javascript"> $(function () { $.getJSON('/api/values/1?timestamp=' + new Date().getTime(), function (data) { alert(data); }); }); </script> }
اکنون برنامه را اجرا کنید. هنگام فراخوانی متد Get، وهلهی سرویس مورد نظر، نال نیست. همچنین متد Dispose نیز به صورت خودکار فراخوانی میشود.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
WebApiDISample.zip
برای مثال در برنامههای ASP.NET Core، یک چنین فرمی:
<form asp-controller="Manage" asp-action="ChangePassword" method="post"> <!-- Form details --> </form>
<form method="post" action="/Manage/ChangePassword"> <!-- Form details --> <input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkSldwD9CpLR...LongValueHere!" /> </form>
تولید خودکار کوکیهای Anti-forgery tokens برای برنامههای Angular
در سمت Angular، مطابق مستندات رسمی آن (^ و ^)، اگر کوکی تولید شدهی توسط برنامه، دارای نام مشخص «XSRF-TOKEN» باشد، کتابخانهی HTTP آن به صورت خودکار مقدار آنرا استخراج کرده و به درخواست بعدی ارسالی آن اضافه میکند. بنابراین در سمت ASP.NET Core تنها کافی است کوکی مخصوص فوق را تولید کرده و به Response اضافه کنیم. مابقی آن توسط Angular به صورت خودکار مدیریت میشود.
میتوان اینکار را مستقیما داخل متد Configure کلاس آغازین برنامه انجام داد و یا بهتر است جهت حجیم نشدن این فایل و مدیریت مجزای این مسئولیت، یک میانافزار مخصوص آنرا تهیه کرد:
using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; namespace AngularTemplateDrivenFormsLab.Utils { public class AntiforgeryTokenMiddleware { private readonly RequestDelegate _next; private readonly IAntiforgery _antiforgery; public AntiforgeryTokenMiddleware(RequestDelegate next, IAntiforgery antiforgery) { _next = next; _antiforgery = antiforgery; } public Task Invoke(HttpContext context) { var path = context.Request.Path.Value; if (path != null && !path.StartsWith("/api/", StringComparison.OrdinalIgnoreCase)) { var tokens = _antiforgery.GetAndStoreTokens(context); context.Response.Cookies.Append( key: "XSRF-TOKEN", value: tokens.RequestToken, options: new CookieOptions { HttpOnly = false // Now JavaScript is able to read the cookie }); } return _next(context); } } public static class AntiforgeryTokenMiddlewareExtensions { public static IApplicationBuilder UseAntiforgeryToken(this IApplicationBuilder builder) { return builder.UseMiddleware<AntiforgeryTokenMiddleware>(); } } }
- در اینجا ابتدا سرویس IAntiforgery به سازندهی کلاس میان افزار تزریق شدهاست. به این ترتیب میتوان به سرویس توکار تولید توکنهای Antiforgery دسترسی یافت. سپس از این سرویس جهت دسترسی به متد GetAndStoreTokens آن برای دریافت محتوای رشتهای نهایی این توکن استفاده میشود.
- اکنون که به این توکن دسترسی پیدا کردهایم، تنها کافی است آنرا با کلید مخصوص XSRF-TOKEN که توسط Angular شناسایی میشود، به مجموعهی کوکیهای Response اضافه کنیم.
- علت تنظیم مقدار خاصیت HttpOnly به false، این است که کدهای جاوا اسکریپتی Angular بتوانند به مقدار این کوکی دسترسی پیدا کنند.
پس از تدارک این مقدمات، کافی است متد الحاقی کمکی UseAntiforgeryToken فوق را به نحو ذیل به متد Configure کلاس آغازین برنامه اضافه کنیم؛ تا کار نصب میان افزار AntiforgeryTokenMiddleware، تکمیل شود:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseAntiforgeryToken();
پردازش خودکار درخواستهای ارسالی از طرف Angular
تا اینجا برنامهی سمت سرور ما کوکیهای مخصوص Angular را با کلیدی که توسط آن شناسایی میشود، تولید کردهاست. در پاسخ، Angular این کوکی را در هدر مخصوصی به نام «X-XSRF-TOKEN» به سمت سرور ارسال میکند (ابتدای آن یک X اضافهتر دارد).
به همین جهت به متد ConfigureServices کلاس آغازین برنامه مراجعه کرده و این هدر مخصوص را معرفی میکنیم تا دقیقا مشخص گردد، این توکن از چه قسمتی باید جهت پردازش استخراج شود:
public void ConfigureServices(IServiceCollection services) { services.AddAntiforgery(x => x.HeaderName = "X-XSRF-TOKEN"); services.AddMvc(); }
یک نکته: اگر میخواهید این کلیدهای هدر پیش فرض Angular را تغییر دهید، باید یک CookieXSRFStrategy سفارشی را برای آن تهیه کنید.
اعتبارسنجی خودکار Anti-forgery tokens در برنامههای ASP.NET Core
ارسال کوکی اطلاعات Anti-forgery tokens و سپس دریافت آن توسط برنامه، تنها یک قسمت از کار است. قسمت بعدی، بررسی معتبر بودن آنها در سمت سرور است. روش متداول انجام اینکار، افزودن ویژگی [ValidateAntiForgeryToken] به هر اکشن متد مزین به [HttpPost] است:
[HttpPost] [ValidateAntiForgeryToken] public IActionResult ChangePassword() { // ... return Json(…); }
public void ConfigureServices(IServiceCollection services) { services.AddAntiforgery(x => x.HeaderName = "X-XSRF-TOKEN"); services.AddMvc(options => { options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); }); }
یک نکته: در این حالت بررسی سراسری، اگر در موارد خاصی نیاز به این اعتبارسنجی خودکار نبود، میتوان از ویژگی [IgnoreAntiforgeryToken] استفاده کرد.
آزمایش برنامه
برای آزمایش مواردی را که تا کنون بررسی کردیم، همان مثال «فرمهای مبتنی بر قالبها در Angular - قسمت پنجم - ارسال اطلاعات به سرور» را بر اساس نکات متدهای ConfigureServices و Configure مطلب جاری تکمیل میکنیم. سپس برنامه را اجرا میکنیم:
همانطور که ملاحظه میکنید، در اولین بار درخواست برنامه، کوکی مخصوص Angular تولید شدهاست.
در ادامه اگر فرم را تکمیل کرده و ارسال کنیم، وجود هدر ارسالی از طرف Angular مشخص است و همچنین خروجی هم با موفقیت دریافت شدهاست:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-template-driven-forms-lab-09.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس به ریشهی پروژه وارد شده و دو پنجرهی کنسول مجزا را باز کنید. در اولی دستورات
>npm install >ng build --watch
>dotnet restore >dotnet watch run
public IActionResult FileStreamActionResult() { var fileStream = System.IO.File.OpenRead(@"D:\path\Controllers\HomeController.cs"); return new FileStreamResult(fileStream, "text/plain") { FileDownloadName = "HomeController.cs" }; }
بررسی آماری واژههای بکار رفته در شاهنامه
مرحله اول: ایجاد ایندکس
using System; using System.Collections.Generic; using System.IO; using Lucene.Net.Analysis.Standard; using Lucene.Net.Documents; using Lucene.Net.Index; using Lucene.Net.Store; namespace ShaahnamehAnalysis { public static class CreateIndex { static readonly Lucene.Net.Util.Version _version = Lucene.Net.Util.Version.LUCENE_CURRENT; static HashSet<string> getStopWords() { var result = new HashSet<string>(); var stopWords = new[] { "به", "با", "از", "تا", "و", "است", "هست", "هستم", "هستیم", "هستید", "هستند", "نیست", "نیستم", "نیستیم", "نیستند", "اما", "یا", "این", "آن", "اینجا", "آنجا", "بود", "باد", "برای", "که", "دارم", "داری", "دارد", "داریم", "دارید", "دارند", "چند", "را", "ها", "های", "می", "هم", "در", "باشم", "باشی", "باشد", "باشیم", "باشید", "باشند", "اگر", "مگر", "بجز", "جز", "الا", "اینکه", "چرا", "کی", "چه", "چطور", "چی", "چیست", "آیا", "چنین", "اینچنین", "نخست", "اول", "آخر", "انتها", "صد", "هزار", "میلیون", "ملیون", "میلیارد", "ملیارد", "یکهزار", "تریلیون", "تریلیارد", "میان", "بین", "زیر", "بیش", "روی", "ضمن", "همانا", "ای", "بعد", "پس", "قبل", "پیش", "هیچ", "همه", "واما", "شد", "شده", "شدم", "شدی", "شدیم", "شدند", "یک", "یکی", "نبود", "میکند", "میکنم", "میکنیم", "میکنید", "میکنند", "میکنی", "طور", "اینطور", "آنطور", "هر", "حال", "مثل", "خواهم", "خواهی", "خواهد", "خواهیم", "خواهید", "خواهند", "داشته", "داشت", "داشتی", "داشتم", "داشتیم", "داشتید", "داشتند", "آنکه", "مورد", "کنید", "کنم", "کنی", "کنند", "کنیم", "نکنم", "نکنی", "نکند", "نکنیم", "نکنید", "نکنند", "نکن", "بگو", "نگو", "مگو", "بنابراین", "بدین", "من", "تو", "او", "ما", "شما", "ایشان", "ی", "ـ", "هایی", "خیلی", "بسیار", "1", "بر", "l", "شود", "کرد", "کرده", "نیز", "خود", "شوند", "اند", "داد", "دهد", "گشت", "ز", "گفت", "آمد", "اندر", "چون", "بد", "چو", "همی", "پر", "سوی", "دو", "گر", "بی", "گرد", "زین", "کس", "زان", "جای", "آید" }; foreach (var item in stopWords) result.Add(item); return result; } public static void CreateShaahnamehIndex(string file = "shaahnameh.txt") { var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex")); var analyzer = new StandardAnalyzer(_version, getStopWords()); using (var writer = new IndexWriter(directory, analyzer, create: true, mfl: IndexWriter.MaxFieldLength.UNLIMITED)) { var section = string.Empty; foreach (var line in File.ReadAllLines(file)) { int result; if (int.TryParse(line, out result)) { var postDocument = new Document(); postDocument.Add(new Field("Id", result.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); postDocument.Add(new Field("Body", section, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS)); writer.AddDocument(postDocument); section = string.Empty; } else section += line; } writer.Optimize(); writer.Commit(); writer.Close(); directory.Close(); } } } }
با ایجاد ایندکسهای لوسین پیشتر در این سایت آشنا شدهاید . روش کار نیز همانند سابق است. اطلاعات خود را، به هر فرمتی که تهیه شده باید تبدیل به اشیاء Document لوسین کرد. برای مثال در اینجا فقط یک فایل txt داریم که تشکیل شده است از تمام صفحات. به ازای هر صفحه، یک شیء Document تهیه و نوشته خواهد شد. همچنین در تهیه ایندکس از یک سری از واژههای بسیار متداول مانند «از»، «به»، «اندر»، (stopWords) صرفنظر شده است.
مرحله دوم: ایجاد ابر واژهها
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Lucene.Net.Index; using Lucene.Net.Store; namespace ShaahnamehAnalysis { [DebuggerDisplay("{Frequency}, {Text}")] public class Tag { public string Text { set; get; } /// <summary> /// The frequency of a term is defined as the number of /// documents in which a specific term appears. /// </summary> public int Frequency { set; get; } } public static class WordsCloud { /// <summary> /// Create Words Cloud /// </summary> /// <param name="threshold">every term that appears in more than x Body</param> public static IList<Tag> Create(int threshold = 200) { var path = Environment.CurrentDirectory + "\\LuceneIndex"; var results = new List<Tag>(); var field = "Body"; IndexReader indexReader = IndexReader.Open(FSDirectory.Open(path ), true); var termFrequency = indexReader.Terms(); while (termFrequency.Next()) { if (termFrequency.DocFreq() >= threshold && termFrequency.Term.Field == field) { results.Add(new Tag { Text = termFrequency.Term.Text, Frequency = termFrequency.DocFreq() }); } } return results.OrderByDescending(x => x.Frequency).ToList(); } } }
پس از اینکه ایندکس لوسین تهیه شد، میتوان به مداخل موجود در آن توسط متد indexReader.Terms دسترسی یافت.
نکته جالب آن فراهم بودن DocFreq هر واژه ایندکس شده است (فرکانس تکرار واژه؛ تعداد اشیاء Document ایی که واژه مورد نظر در آنها تکرار شده است). برای مثال در اینجا اگر واژهای 200 بار یا بیشتر در صفحات مختلف شاهنامه تکرار شده باشد، به عنوان یک واژه پر اهمیت انتخاب شده و به ابر واژههای نهایی اضافه میگردد.
مرحله سوم: استفاده از نتایج
using System; using System.Diagnostics; using System.IO; using System.Linq; namespace ShaahnamehAnalysis { class Program { static void Main(string[] args) { CreateIndex.CreateShaahnamehIndex(); var wordsCloudList = WordsCloud.Create(); var data = wordsCloudList.Select(x => x.Text + ", " + x.Frequency) .Aggregate((s1, s2) => s1 + Environment.NewLine + s2); var output = "ShaahnamehAnalysis.txt"; File.WriteAllText(output, data); Process.Start(output); } } }
که نتیجه 15 مورد اول آن به صورت زیر است:
واژه | فرکانس
شاه, 1191
دل, 1088
سر, 1070
کار, 840
لشکر, 801
تخت, 755
روز, 745
ایران, 740
جهان, 724
مرد, 660
دست, 630
تاج, 623
نزدیک, 623
گیتی, 585
راه, 584
فایلهای کامل این مثال را از اینجا میتوانید دریافت کنید:
ShaahnamehAnalysis.zip
<script> let src = 'https://svelte.dev/tutorial/image.gif'; let name = 'Rick Astley'; </script> <img src={src} alt="{name} dancing">
- نکته اول : اگر در تگ img مقدار alt را وارد نکنیم و یا alt در این تگ وجود نداشته باشد، یک هشدار توسط کامپایلر svelte برای ما با عنوان <img> element should have an alt attribute> ایجاد میشود. زمان ساخت یک برنامه بسیار مهم است تا قوانین نوشتن یک کد html خوب را رعایت کنیم تا برای تمامی کاربران احتمالی برنامه قابل استفاده باشد. در همین مثال با ایجاد یک هشدار Svelte تلاش میکند که ما را از اشتباه در نوشتن کد html مطلع سازد.
- نکته دوم : اگر نام یک آبجکت تعریف شده و یک attribute، برابر باشد میتوانیم از نسخه کوتاه شده یا Shorthand attributes در svelte استفاده کنیم. به طور مثال در مثال بالا میتوانیم از کد زیر در خط 6 استفاده کنیم.
<img {src} alt="{name} dancing">
<script> export let siteName = "dotnettips"; </script> <p>this is a nested component for third tutorial on {siteName}</p>
<script> import Nested from "./Nested.svelte"; export let name; </script> <h1>Hello {name}!</h1> <Nested siteName="dotnettips.info" />
Hello world! this is a nested component for third tutorial on dotnettips.info
در مثال بالا ما یک کامپوننت جدید را ایجاد کرده و از طریق دستور import به App.svelte اضافه کردیم. نکتهای که در اینجا وجود دارد، نحوه مقدار دهی props در کامپوننتها است. اگر به خط 9 دقت کنیم، کامپوننت ما از طریق تگ جدیدی با نام (Nested) به بدنه html برنامه اضافه شده است که یک attribute به نام siteName دارد. siteName متغیر export شده در کامپوننت Nested.svelte است که در کامپوننتها به این صورت مقدار دهی میشود. قبلا نحوه مقدار دهی این خصیصهها را در فایلهای جاوا اسکریپت مشاهده کرده بودیم. نکته دیگری که باید به آن دقت داشت این است که خصیصه siteName مقدار پیش فرض dotnettips را در Nested.svelte به خود اختصاص داده بود. به همین جهت اگر ما siteName را هنگام استفاده از کامپوننت مقدار دهی نکنیم، از مقدار پیش فرض خود استفاده خواهد کرد. ولی اینجا ما با مقدار دهی آن، siteName را به dotnettips.info تغییر دادهایم.
نکته مهم : دقت داشته باشید کامپوننتهای شما همیشه باید با حروف بزرگ شروع شوند؛ به طور مثال در صورت نوشتن <nested/> محتوای کامپوننت نمایش داده نخواهد شد. svelte، از طریق زیر نظر گرفتن حروف کوچک و بزرگ در ابتدای تگها، بین تگهای html و کامپوننتها تمایز قائل میشود.
Spread props :
تا اینجا به صورت خلاصه با props یا خصیصهها آشنا شدهاید و دیدیم که با export کردن یک متغیر در یک کامپوننت، میتوانیم آن را هنگام استفاده مقدار دهی نماییم. برای اینکه تمرینی هم باشد با توجه به مطالبی که تاکنون گفته شده، پروژهی جدیدی را ایجاد کنید و محتوای App.svelte را مانند کد زیر تغییر دهید.
<script> import Info from './Info.svelte'; const pkg = { name: 'svelte', version: 3, speed: 'blazing', website: 'https://svelte.dev' }; </script> <Info name={pkg.name} version={pkg.version} speed={pkg.speed} website={pkg.website}/>
همانطور که در خط دوم کد میبینید، کامپوننتی به نام Info.svelte به این بخش اضافه شدهاست. این کامپوننت را با محتوای زیر ایجاد نمایید:
<script> export let name; export let version; export let speed; export let website; </script> <p> The <code>{name}</code> package is {speed} fast. Download version {version} from <a href="https://www.npmjs.com/package/{name}">npm</a> and <a href={website}>learn more here</a> </p>
اگر برنامه را اجرا کنید یک چنین خروجی را مشاهده خواهید کرد:
The svelte package is blazing fast. Download version 3 from npm and learn more here
<Info {...pkg}/>
<script> let count = 0; function handleClick() { count += 1; } </script> <p>Count : {count}</p> <button on:click={handleClick}> Clicked {count} {count === 1 ? 'time' : 'times'} </button>
<script> let count = 0; let doubled = count * 2; function handleClick() { count += 1; } </script> <button on:click={handleClick}> Clicked {count} {count === 1 ? 'time' : 'times'} </button> <p>{count} doubled is {doubled}</p>
$: doubled = count * 2;
$: console.log(`the count is ${count}`);
$: { console.log(`the count is ${count}`); alert(`I SAID THE COUNT IS ${count}`); }
$: if (count >= 10) { alert(`count is dangerously high!`); count = 9; }
<script> let numbers = [1, 2, 3, 4]; function addNumber() { let newNumber = numbers.length + 1; numbers.push(newNumber); } $: sum = numbers.reduce((t, n) => t + n, 0); </script> <p>{numbers.join(' + ')} = {sum}</p> <button on:click={addNumber}>Add a number</button>
function addNumber() { let newNumber = numbers.length + 1; numbers.push(newNumber); numbers = numbers; }
function addNumber() { let newNumber = numbers.length + 1; numbers = [...numbers, newNumber]; }
مروری بر Two way bindings :
<script> let name = ""; function updateName(event) { name = event.target.value; } </script> <h4>My Name Is {name}</h4> <input value={name} on:input={updateName} />
<script> let name = ""; </script> <h4>My Name Is {name}</h4> <input bind:value={name} />
{#if condition} <!-- you html codes ... --> {/if}
<script> let user = { loggedIn: false }; function toggle() { user.loggedIn = !user.loggedIn; } </script> {#if user.loggedIn} <button on:click={toggle}> Log out </button> {/if} {#if !user.loggedIn} <button on:click={toggle}> Log in </button> {/if}
{#if condition} <!-- you html code when condition is true --> {:else} <!-- you html code when condition is false --> {/if}
{#if user.loggedIn} <button on:click={toggle}> Log out </button> {:else} <button on:click={toggle}> Log in </button> {/if}
{#if condition} <!-- you html code when condition is true --> {:else if condition2} <!-- you html code when condition2 is true --> {:else} <!-- you html code when condition and condition2 are false --> {/if}
{#each list as item} <!-- you html code per each item in list --> {/each}
<script> let cats = [ { id: 'J---aiyznGQ', name: 'Keyboard Cat' }, { id: 'z_AbfPXTKms', name: 'Maru' }, { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' } ]; </script> <h1>The Famous Cats of YouTube</h1> <ul> {#each cats as cat} <li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}"> {cat.name} </a></li> {/each} </ul>
<ul> {#each cats as {id,name}} <li><a target="_blank" href="https://www.youtube.com/watch?v={id}"> {name} </a></li> {/each} </ul>
<ul> {#each cats as { id, name }, i} <li><a target="_blank" href="https://www.youtube.com/watch?v={id}"> {i + 1}: {name} </a></li> {/each} </ul>
نکته : در این بخش من سعی کردم تا حدودی به ترتیب بخش آموزشی خود وبسایت Svelte، موارد را بیان کنم؛ ولی با توجه به اینکه شاید دوستان ترجیح بدهند روش آموزشی خود آن وبسایت که امکان تغییر و نوشتن کد را هم محیا کرده است، امتحان کنند لینک آن را به اشتراک میگذارم.
- یک ASP.NET Web Application با نام NorthwindService ایجاد نمایید و بر روی پروژه راست کلیک کنید و از منوی Add گزینه New Item را انتخاب نمایید از پنجره باز شده از دسته Data گزینه ADO.NET Entity Data Model را انتخاب و نام ان را Northwind بگذارید.
- از پنجره باز شده Generate from Databaseرا انتخاب و با انتخاب کانکشن از نوع Sql Server Compact 4 اتصال به فایل Northwind.sdf را انتخاب تا کلاسهای لازم تولید شود.
- برای تولید data service بر روی پروژه راست کلیک کنید و از منوی Add گزینه New Item را انتخاب نمایید از پنجره باز شده گزینه
WCF Data Service را انتخاب و نام آن را Northwind.svc بگذارید. کد زیر خودکار تولید میشود
public class Northwind : DataService< /* TODO: put your data source class name here */ > { // This method is called only once to initialize service-wide policies. public static void InitializeService(DataServiceConfiguration config) { // TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc. // Examples: // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead); // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3; } }
- برای دسترسی به موجودیتهای Northwind بجای عبارت put your data source نام مدل را تایپ کنید
public class Northwind : DataService<NorthwindEntities>
- برای فعال کردن دسترسی به منابع data source متغیر config کلاس DataServiceConfiguration را بصورت زیر تنظیم نمایید. تابع SetEntitySetAccessRule با گرفتن نام موجودیت و نحوه دسترسی امکان استفاده از این موجودیت را با استفاده از WCF Data Service فزاهم مینمایید. مثلا در زیر امکان دسترسی به موجودیت Orders را با امکان خواندن همه، نوشتن ادقامی و جایگزین فراهم نموده است.
config.SetEntitySetAccessRule("Orders", EntitySetRights.AllRead | EntitySetRights.WriteMerge | EntitySetRights.WriteReplace ); config.SetEntitySetAccessRule("Customers", EntitySetRights.AllRead);
- اگر بخواهیم امکان خواندن همه موجودیتها را فراهم کنیم از کد زیر میتوانیم استفاده نمایید که * به معنای همه موجودیتهای data model میباشد
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
<service xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom" xml:base="http://localhost:8358/Northwind.svc/"> <workspace> <atom:title>Default</atom:title> <collection href="Categories"> <atom:title>Categories</atom:title> </collection> <collection href="Customers"> <atom:title>Customers</atom:title> </collection> <collection href="Employees"> <atom:title>Employees</atom:title> </collection> <collection href="Order_Details"> <atom:title>Order_Details</atom:title> </collection> <collection href="Orders"> <atom:title>Orders</atom:title> </collection> <collection href="Products"> <atom:title>Products</atom:title> </collection> <collection href="Shippers"> <atom:title>Shippers</atom:title> </collection> <collection href="Suppliers"> <atom:title>Suppliers</atom:title> </collection> </workspace> </service>
- بر روی Solution پروژه جاری راست کلیک و از منوی Add گزینه New Project را انتخاب و یک پروژه از نوع WPF Application با نام NorthwindClient ایجاد نمایید.
- در پنجره MainWindow مانند کد زیر از یک Combobox و DataGrid برای نمایش اطلاعات استفاده نمایید
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Northwind Orders" Height="335" Width="425" Name="OrdersWindow" Loaded="Window1_Loaded"> <Grid Name="orderItemsGrid"> <ComboBox DisplayMemberPath="Order_ID" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="true" Height="23" Margin="92,12,198,0" Name="comboBoxOrder" VerticalAlignment="Top"/> <DataGrid ItemsSource="{Binding Path=Order_Details}" CanUserAddRows="False" CanUserDeleteRows="False" Name="orderItemsDataGrid" Margin="34,46,34,50" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Header="Product" Binding="{Binding Product_ID, Mode=OneWay}" /> <DataGridTextColumn Header="Quantity" Binding="{Binding Quantity, Mode=TwoWay}" /> <DataGridTextColumn Header="Price" Binding="{Binding UnitPrice, Mode=TwoWay}" /> <DataGridTextColumn Header="Discount" Binding="{Binding Discount, Mode=TwoWay}" /> </DataGrid.Columns> </DataGrid> <Label Height="28" Margin="34,12,0,0" Name="orderLabel" VerticalAlignment="Top" HorizontalAlignment="Left" Width="65">Order:</Label> <StackPanel Name="Buttons" Orientation="Horizontal" HorizontalAlignment="Right" Height="40" Margin="0,257,22,0"> <Button Height="23" HorizontalAlignment="Right" Margin="0,0,12,12" Name="buttonSave" VerticalAlignment="Bottom" Width="75" Click="buttonSaveChanges_Click">Save Changes </Button> <Button Height="23" Margin="0,0,12,12" Name="buttonClose" VerticalAlignment="Bottom" Width="75" Click="buttonClose_Click">Close</Button> </StackPanel> </Grid> </Window>
- برای ارجاع به wcf data service بر روی پروژه راست کلیک و گزینه Add Service Reference را انتخاب نمایید در پنجره باز شده گزینه Discover را انتخاب تا سرویس را یافته و نام Namespase را Northwind بگذارید.
- حال مانند کد زیر یک شی از مدل NorthwindEntities با آدرس وب سرویس ایجاد نموده ایم و نتیحه کوئری با استفاده از کلاس DataServiceCollection به DataContext گرید انتصاب داده ایم که البته پیش فرض آن آشنایی با DataBinding در WPF است.
private NorthwindEntities context; private string customerId = "ALFKI"; private Uri svcUri = new Uri("http://localhost:8358/Northwind.svc"); private void Window1_Loaded(object sender, RoutedEventArgs e) { try { context = new NorthwindEntities(svcUri); var ordersQuery = from o in context.Orders.Expand("Order_Details") where o.Customers.Customer_ID == customerId select o; DataServiceCollection<Orders> customerOrders = new DataServiceCollection<Orders>(ordersQuery); this.orderItemsGrid.DataContext = customerOrders; } catch (Exception ex) { MessageBox.Show(ex.ToString()); } }
- با صدا زدن تابع SaveChanges مدل میتوانید تغییرات را در پایگاه داده ذخیره نمایید.
private void buttonSaveChanges_Click(object sender, RoutedEventArgs e) { try { context.SaveChanges(); } catch (DataServiceRequestException ex) { MessageBox.Show(ex.ToString()); } }
- برنامه را اجرا نمایید تا خروجی کار را مشاهده نمایید. مقادیر Quantity را تغییر دهید و دکمه Save Changes را انتخاب تا تغییرات دخیره شود.
Kendo UI Q1 2015 منتشر شد
آخرین نگارش آنرا از CDN مرتبط میتوانید دریافت کنید:
<link rel="stylesheet" href="http://cdn.kendostatic.com/2015.1.318/styles/kendo.common-material.min.css" /> <link rel="stylesheet" href="http://cdn.kendostatic.com/2015.1.318/styles/kendo.material.min.css" /> <link rel="stylesheet" href="http://cdn.kendostatic.com/2015.1.318/styles/kendo.dataviz.min.css" /> <link rel="stylesheet" href="http://cdn.kendostatic.com/2015.1.318/styles/kendo.dataviz.material.min.css" /> <script src="http://cdn.kendostatic.com/2015.1.318/js/jquery.min.js"></script> <script src="http://cdn.kendostatic.com/2015.1.318/js/kendo.all.min.js"></script>
قبل از آن بد نیست که بدانید چرا این ابزار T4 نام گرفته !
T4 مخفف Text Template Transformation Toolkit میباشد (TTTT). شکل زیر مراحل اجرای یک کد Text Template را توسط T4 نشان میدهد:
برای آزمایش، یک فایل متنی کنار فایل TextTransform.exe با نام Text2.tt ایجاد نمایید و کد زیر را در داخل آن بنویسید:
<#@ template debug="true" hostspecific="false" language="C#" #> <#@ output extension=".txt" #> <#@ import namespace="System.Diagnostics" #> Report In : <#= DateTime.Now #> <# Process[] Procs = Process.GetProcesses(); for (int i = 0; i < Procs.Length; i++) { string Pstr = Procs[i].ProcessName + " -|- " + Procs[i].Id + Environment.NewLine ; #><#= Pstr #><# } #>
برای تولید فایل خروجی، دستور زیر را در cmd.exe اجرا کنید :
نکته: اگر User شما به این پوشه دسترسی ندارد و کاربر Admin نیستید احتمالا به مشکل بر میخورد. میتوانید فایل TextTransform.exe را در مکان دیگری قرار دهید و دستور را از آن محل اجرا کنید و یا برای پوشهی مذکور دسترسی ایجاد نمایید.
اگر میخواهید بیشتر در مورد معماری T4 بدانید بهتر است مقاله زیر را مطالعه کنید:
http://www.olegsych.com/2008/05/t4-architecture/
نکته دیگر این که برای Visual Studio، ابزارهایی جهت بهبود کار با Text Templateها وجود دارند که با جستجوی T4 Editor، نمونههایی از آنها را خواهید یافت. tangible T4 Editor نمونه ای از این Pluginها میباشد که به Visual Studio افزوده میگردد و یا یک پروژه Open Source هم برای آشنایی بسیار بیشتر با T4 در t4toolbox.codeplex.com وجود دارد که میتوانید مشاهده کنید.Public Class User { public string Id { get; set; } public string PhoneNumber { get; set; } public Dictionary<string, App> Apps { get; set; } } public class App { public string FirstName { get; set; } public string LastName { get; set; } public string UserName { get; set; } public List<string> Roles { get; set; } public List<String> Messages { get; set; } public String AdressId { get; set; } public bool IsActive { get; set; } = true; [JsonIgnore] public string DisplayName => $"{FirstName} {LastName}"; }
services.AddSingleton<IDocumentStore>(serviceProvider => { var store = new DocumentStore() { Urls = new[] { "http://192.168.1.10:8080" }, Database = "AccountingSystem", }.Initialize(); return store; }); services.AddScoped<IAsyncDocumentSession>(serviceProvider => { var store = serviceProvider.GetRequiredService<IDocumentStore>(); return store.OpenAsyncSession(); });
var user = new User { PhoneNumber = user.PhoneNumber }; user.Apps.Add(appCode, new ActiveApp { FirstName = "عبدالصالح", LastName = "کاشانی", UserName = abdossaleh, IsActive = true, RolesId = new List<string>{"Admin"} }); await _documentSession.StoreAsync(user); await _documentSession.SaveChangesAsync()
var user = await _documentSession.LoadAsync<User>("Users/131-A");
_documentSession.Advanced.Patch<User, string>("Users/131-A", u => u.PhoneNumber , "09131110000");
_documentSession.Advanced.Patch<User, string>("Users/131-A", u => u.Apps["59"].RolesId , r => r.Add("Admin"));
_documentSession.Advanced.Increment<User, int>("Users/131-A", x => x.TestProp, 10);
_documentSession.Advanced.Defer(new PatchCommandData("Users/131-A", null, new PatchRequest() { Script = $@"this.Apps[args.appCode] = args.app", Values = { {"appCode", appCode}, {"app", new ActiveApp { FirstName = "عبدالصالح", LastName = "کاشانی", UserName = abdossaleh, RolesId = new List<string>{"Admin"} } } } }, null));
Script = "this.Apps[args.app].Roles.splice(args.index,0,args.role)", Values = { { "index": 1 // مکانی که میخواهیم عملیات انجام شود "app", 59 "role", "User" } }
splice(args.index,1,args.role)
Script = @"this.Roles= this.Apps[args.app].Roles.filter(role=> role != args.role);", Values = { {"app", 59} {"role", "User"} }