- .NET Core
- Node.js
- Docker
- Elasticsearch: A distributed and open source search engine based on Lucene. A blazing fast NoSQL database with replication capabilities, it is the most widely known component of the ELK stack, together with Kibana (for reporting and visualizations), Logstash (for data import) and Beats (for data shipping). Even Azure Search uses it behind the covers. Free but some tools are paid. Get it from http://elastic.co.
- ECMAScript 2015
- HTML5
- Kafka
- TypeScript
- MongoDB
- Git
- Nginx
- Octopus Deploy
- Azure
- Amazon Web Services
- Linux
- Visual Studio Code
- Xamarin
- Google Analytics
- SQL Server 2016
- Let’s Encrypt
- TensorFlow
- GitLab
- Redis
Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self' <URL> <URL>". Either the 'unsafe-inline' keyword, a hash ('sha256-e89EFOm4894OkHmgoH52lEUIFeaK8fITnql0='), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present.
در قسمت قبلی بروز رسانی موجودیتهای منفصل با WCF را بررسی کردیم. در این قسمت خواهیم دید چگونه میتوان تغییرات موجودیتها را تشخیص داد و عملیات CRUD را روی یک Object Graph اجرا کرد.
تشخیص تغییرات با Web API
فرض کنید میخواهیم از سرویسهای Web API برای انجام عملیات CRUD استفاده کنیم، اما بدون آنکه برای هر موجودیت متدهایی مجزا تعریف کنیم. به بیان دیگر میخواهیم عملیات مذکور را روی یک Object Graph انجام دهیم. مدیریت دادهها هم با مدل Code-First پیاده سازی میشود. در مثال جاری یک اپلیکیشن کنسول خواهیم داشت که بعنوان یک کلاینت سرویس را فراخوانی میکند. هر پروژه نیز در Solution مجزایی قرار دارد، تا یک محیط n-Tier را شبیه سازی کنیم.
مدل زیر را در نظر بگیرید.
همانطور که میبینید مدل ما آژانسهای مسافرتی و رزرواسیون آنها را ارائه میکند. میخواهیم مدل و کد دسترسی دادهها را در یک سرویس Web API پیاده سازی کنیم تا هر کلاینتی که به HTTP دسترسی دارد بتواند عملیات CRUD را انجام دهد. برای ساختن سرویس مورد نظر مراحل زیر را دنبال کنید:
- در ویژوال استودیو پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب پروژه را Web API انتخاب کنید. نام پروژه را به Recipe3.Service تغییر دهید.
- کنترلر جدیدی بنام TravelAgentController به پروژه اضافه کنید.
- دو کلاس جدید با نامهای TravelAgent و Booking بسازید و کد آنها را مطابق لیست زیر تغییر دهید.
public class TravelAgent { public TravelAgent() { this.Bookings = new HashSet<Booking>(); } public int AgentId { get; set; } public string Name { get; set; } public virtual ICollection<Booking> Bookings { get; set; } } public class Booking { public int BookingId { get; set; } public int AgentId { get; set; } public string Customer { get; set; } public DateTime BookingDate { get; set; } public bool Paid { get; set; } public virtual TravelAgent TravelAgent { get; set; } }
- با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
- کلاس جدیدی بنام Recipe3Context بسازید و کد آن را مطابق لیست زیر تغییر دهید.
public class Recipe3Context : DbContext { public Recipe3Context() : base("Recipe3ConnectionString") { } public DbSet<TravelAgent> TravelAgents { get; set; } public DbSet<Booking> Bookings { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<TravelAgent>().HasKey(x => x.AgentId); modelBuilder.Entity<TravelAgent>().ToTable("TravelAgents"); modelBuilder.Entity<Booking>().ToTable("Bookings"); } }
- فایل Web.config پروژه را باز کنید و رشته اتصال زیر را به قسمت ConnectionStrings اضافه کنید.
<connectionStrings> <add name="Recipe3ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
- فایل Global.asax را باز کنید و کد زیر را به متد Application_Start اضافه نمایید. این کد بررسی Model Compatibility در EF را غیرفعال میکند. همچنین به JSON serializer میگوییم که self-referencing loop خاصیتهای پیمایشی را نادیده بگیرد. این حلقه بدلیل ارتباط bidirectional بین موجودیتها بوجود میآید.
protected void Application_Start() { // Disable Entity Framework Model Compatibilty Database.SetInitializer<Recipe1Context>(null); // The bidirectional navigation properties between related entities // create a self-referencing loop that breaks Web API's effort to // serialize the objects as JSON. By default, Json.NET is configured // to error when a reference loop is detected. To resolve problem, // simply configure JSON serializer to ignore self-referencing loops. GlobalConfiguration.Configuration.Formatters.JsonFormatter .SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; ... }
- فایل RouteConfig.cs را باز کنید و قوانین مسیریابی را مانند لیست زیر تغییر دهید.
public static void Register(HttpConfiguration config) { config.Routes.MapHttpRoute( name: "ActionMethodSave", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional }); }
- در آخر کنترلر TravelAgent را باز کنید و کد آن را مطابق لیست زیر بروز رسانی کنید.
public class TravelAgentController : ApiController { // GET api/travelagent [HttpGet] public IEnumerable<TravelAgent> Retrieve() { using (var context = new Recipe3Context()) { return context.TravelAgents.Include(x => x.Bookings).ToList(); } } /// <summary> /// Update changes to TravelAgent, implementing Action-Based Routing in Web API /// </summary> public HttpResponseMessage Update(TravelAgent travelAgent) { using (var context = new Recipe3Context()) { var newParentEntity = true; // adding the object graph makes the context aware of entire // object graph (parent and child entities) and assigns a state // of added to each entity. context.TravelAgents.Add(travelAgent); if (travelAgent.AgentId > 0) { // as the Id property has a value greater than 0, we assume // that travel agent already exists and set entity state to // be updated. context.Entry(travelAgent).State = EntityState.Modified; newParentEntity = false; } // iterate through child entities, assigning correct state. foreach (var booking in travelAgent.Bookings) { if (booking.BookingId > 0) // assume booking already exists if ID is greater than zero. // set entity to be updated. context.Entry(booking).State = EntityState.Modified; } context.SaveChanges(); HttpResponseMessage response; // set Http Status code based on operation type response = Request.CreateResponse(newParentEntity ? HttpStatusCode.Created : HttpStatusCode.OK, travelAgent); return response; } } [HttpDelete] public HttpResponseMessage Cleanup() { using (var context = new Recipe3Context()) { context.Database.ExecuteSqlCommand("delete from [bookings]"); context.Database.ExecuteSqlCommand("delete from [travelagents]"); } return Request.CreateResponse(HttpStatusCode.OK); } }
- در ویژوال استودیو پروژه جدیدی از نوع Console application بسازید و نام آن را به Recipe3.Client تغییر دهید.
- فایل program.cs را باز کنید و کد آن را مطابق لیست زیر بروز رسانی کنید.
internal class Program { private HttpClient _client; private TravelAgent _agent1, _agent2; private Booking _booking1, _booking2, _booking3; private HttpResponseMessage _response; private static void Main() { Task t = Run(); t.Wait(); Console.WriteLine("\nPress <enter> to continue..."); Console.ReadLine(); } private static async Task Run() { var program = new Program(); program.ServiceSetup(); // do not proceed until clean-up is completed await program.CleanupAsync(); program.CreateFirstAgent(); // do not proceed until agent is created await program.AddAgentAsync(); program.CreateSecondAgent(); // do not proceed until agent is created await program.AddSecondAgentAsync(); program.ModifyAgent(); // do not proceed until agent is updated await program.UpdateAgentAsync(); // do not proceed until agents are fetched await program.FetchAgentsAsync(); } private void ServiceSetup() { // set up infrastructure for Web API call _client = new HttpClient {BaseAddress = new Uri("http://localhost:6687/")}; // add Accept Header to request Web API content negotiation to return resource in JSON format _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); } private async Task CleanupAsync() { // call cleanup method in service _response = await _client.DeleteAsync("api/travelagent/cleanup/"); } private void CreateFirstAgent() { // create new Travel Agent and booking _agent1 = new TravelAgent {Name = "John Tate"}; _booking1 = new Booking { Customer = "Karen Stevens", Paid = false, BookingDate = DateTime.Parse("2/2/2010") }; _booking2 = new Booking { Customer = "Dolly Parton", Paid = true, BookingDate = DateTime.Parse("3/10/2010") }; _agent1.Bookings.Add(_booking1); _agent1.Bookings.Add(_booking2); } private async Task AddAgentAsync() { // call generic update method in Web API service to add agent and bookings _response = await _client.PostAsync("api/travelagent/update/", _agent1, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { // capture newly created travel agent from service, which will include // database-generated Ids for each entity _agent1 = await _response.Content.ReadAsAsync<TravelAgent>(); _booking1 = _agent1.Bookings.FirstOrDefault(x => x.Customer == "Karen Stevens"); _booking2 = _agent1.Bookings.FirstOrDefault(x => x.Customer == "Dolly Parton"); Console.WriteLine("Successfully created Travel Agent {0} and {1} Booking(s)", _agent1.Name, _agent1.Bookings.Count); } else Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase); } private void CreateSecondAgent() { // add new agent and booking _agent2 = new TravelAgent {Name = "Perry Como"}; _booking3 = new Booking { Customer = "Loretta Lynn", Paid = true, BookingDate = DateTime.Parse("3/15/2010")}; _agent2.Bookings.Add(_booking3); } private async Task AddSecondAgentAsync() { // call generic update method in Web API service to add agent and booking _response = await _client.PostAsync("api/travelagent/update/", _agent2, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { // capture newly created travel agent from service _agent2 = await _response.Content.ReadAsAsync<TravelAgent>(); _booking3 = _agent2.Bookings.FirstOrDefault(x => x.Customer == "Loretta Lynn"); Console.WriteLine("Successfully created Travel Agent {0} and {1} Booking(s)", _agent2.Name, _agent2.Bookings.Count); } else Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase); } private void ModifyAgent() { // modify agent 2 by changing agent name and assigning booking 1 to him from agent 1 _agent2.Name = "Perry Como, Jr."; _agent2.Bookings.Add(_booking1); } private async Task UpdateAgentAsync() { // call generic update method in Web API service to update agent 2 _response = await _client.PostAsync("api/travelagent/update/", _agent2, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { // capture newly created travel agent from service, which will include Ids _agent1 = _response.Content.ReadAsAsync<TravelAgent>().Result; Console.WriteLine("Successfully updated Travel Agent {0} and {1} Booking(s)", _agent1.Name, _agent1.Bookings.Count); } else Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase); } private async Task FetchAgentsAsync() { // call Get method on service to fetch all Travel Agents and Bookings _response = _client.GetAsync("api/travelagent/retrieve").Result; if (_response.IsSuccessStatusCode) { // capture newly created travel agent from service, which will include Ids var agents = await _response.Content.ReadAsAsync<IEnumerable<TravelAgent>>(); foreach (var agent in agents) { Console.WriteLine("Travel Agent {0} has {1} Booking(s)", agent.Name, agent.Bookings.Count()); } } else Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase); } }
- در آخر کلاسهای TravelAgent و Booking را به پروژه کلاینت اضافه کنید. اینگونه کدها بهتر است در لایه مجزایی قرار گیرند و بین پروژهها به اشتراک گذاشته شوند.
اگر اپلیکیشن کنسول (کلاینت) را اجرا کنید با خروجی زیر مواجه خواهید شد.
(Successfully created Travel Agent John Tate and 2 Booking(s
(Successfully created Travel Agent Perry Como and 1 Booking(s
(Successfully updated Travel Agent Perry Como, Jr. and 2 Booking(s
(Travel Agent John Tate has 1 Booking(s
(Travel Agent Perry Como, Jr. has 2 Booking(s
شرح مثال جاری
با اجرای اپلیکیشن Web API شروع کنید. این اپلیکیشن یک کنترلر MVC Web Controller دارد که پس از اجرا شما را به صفحه خانه هدایت میکند. در این مرحله سایت در حال اجرا است و سرویسها قابل دسترسی هستند.
سپس اپلیکیشن کنسول را باز کنید، روی خط اول کد فایل program.cs یک breakpoint قرار دهید و آن را اجرا کنید. ابتدا آدرس سرویس Web API را نگاشت میکنیم و با تنظیم مقدار خاصیت Accept Header از سرویس درخواست میکنیم که اطلاعات را با فرمت JSON بازگرداند.
بعد از آن با استفاده از آبجکت HttpClient متد DeleteAsync را فراخوانی میکنیم که روی کنترلر TravelAgent تعریف شده است. این متد تمام دادههای پیشین را حذف میکند.
در قدم بعدی سه آبجکت جدید میسازیم: یک آژانس مسافرتی و دو رزرواسیون. سپس این آبجکتها را با فراخوانی متد PostAsync روی آبجکت HttpClient به سرویس ارسال میکنیم. اگر به متد Update در کنترلر TravelAgent یک breakpoint اضافه کنید، خواهید دید که این متد آبجکت آژانس مسافرتی را بعنوان یک پارامتر دریافت میکند و آن را به موجودیت TravelAgents در Context جاری اضافه مینماید. این کار آبجکت آژانس مسافرتی و تمام آبجکتهای فرزند آن را در حالت Added اضافه میکند و باعث میشود که context جاری شروع به ردیابی (tracking) آنها کند.
نکته: قابل ذکر است که اگر موجودیتهای متعددی با مقداری یکسان در خاصیت کلید اصلی (Primary-key value) دارید باید مجموعه آبجکتهای خود را Add کنید و نه Attach. در مثال جاری چند آبجکت Booking داریم که مقدار کلید اصلی آنها صفر است (Bookings with Id = 0). اگر از Attach استفاده کنید EF پیغام خطایی صادر میکند چرا که چند موجودیت با مقادیر کلید اصلی یکسان به context جاری اضافه کرده اید.
بعد از آن بر اساس مقدار خاصیت Id مشخص میکنیم که موجودیتها باید بروز رسانی شوند یا خیر. اگر مقدار این فیلد بزرگتر از صفر باشد، فرض بر این است که این موجودیت در دیتابیس وجود دارد بنابراین خاصیت EntityState را به Modified تغییر میدهیم. علاوه بر این فیلدی هم با نام newParentEntity تعریف کرده ایم که توسط آن بتوانیم کد وضعیت مناسبی به کلاینت بازگردانیم. در صورتی که مقدار فیلد Id در موجودیت TravelAgent برابر با یک باشد، مقدار خاصیت EntityState را به همان Added رها میکنیم.
سپس تمام آبجکتهای فرزند آژانس مسافرتی (رزرواسیون ها) را بررسی میکنیم و همین منطق را روی آنها اعمال میکنیم. یعنی در صورتی که مقدار فیلد Id آنها بزرگتر از 0 باشد وضعیت EntityState را به Modified تغییر میدهیم. در نهایت متد SaveChanges را فراخوانی میکنیم. در این مرحله برای موجودیتهای جدید اسکریپتهای Insert و برای موجودیتهای تغییر کرده اسکریپتهای Update تولید میشود. سپس کد وضعیت مناسب را به کلاینت بر میگردانیم. برای موجودیتهای اضافه شده کد وضعیت 201 (Created) و برای موجودیتهای بروز رسانی شده کد وضعیت 200 (OK) باز میگردد. کد 201 به کلاینت اطلاع میدهد که رکورد جدید با موفقیت ثبت شده است، و کد 200 از بروز رسانی موفقیت آمیز خبر میدهد. هنگام تولید سرویسهای REST-based بهتر است همیشه کد وضعیت مناسبی تولید کنید.
پس از این مراحل، آژانس مسافرتی و رزرواسیون جدیدی میسازیم و آنها را به سرویس ارسال میکنیم. سپس نام آژانس مسافرتی دوم را تغییر میدهیم، و یکی از رزرواسیونها را از آژانس اولی به آژانس دومی منتقل میکنیم. اینبار هنگام فراخوانی متد Update تمام موجودیتها شناسه ای بزرگتر از 1 دارند، بنابراین وضعیت EntityState آنها را به Modified تغییر میدهیم تا هنگام ثبت تغییرات دستورات بروز رسانی مناسب تولید و اجرا شوند.
در آخر کلاینت ما متد Retreive را روی سرویس فراخوانی میکند. این فراخوانی با کمک متد GetAsync انجام میشود که روی آبجکت HttpClient تعریف شده است. فراخوانی این متد تمام آژانسهای مسافرتی بهمراه رزرواسیونهای متناظرشان را دریافت میکند. در اینجا با استفاده از متد Include تمام رکوردهای فرزند را بهمراه تمام خاصیت هایشان (properties) بارگذاری میکنیم.
دقت کنید که مرتب کننده JSON تمام خواص عمومی (public properties) را باز میگرداند، حتی اگر در کد خود تعداد مشخصی از آنها را انتخاب کرده باشید.
نکته دیگر آنکه در مثال جاری از قراردادهای توکار Web API برای نگاشت درخواستهای HTTP به اکشن متدها استفاده نکرده ایم. مثلا بصورت پیش فرض درخواستهای POST به متدهایی نگاشت میشوند که نام آنها با "Post" شروع میشود. در مثال جاری قواعد مسیریابی را تغییر داده ایم و رویکرد مسیریابی RPC-based را در پیش گرفته ایم. در اپلیکیشنهای واقعی بهتر است از قواعد پیش فرض استفاده کنید چرا که هدف Web API ارائه سرویسهای REST-based است. بنابراین بعنوان یک قاعده کلی بهتر است متدهای سرویس شما به درخواستهای متناظر HTTP نگاشت شوند. و در آخر آنکه بهتر است لایه مجزایی برای میزبانی کدهای دسترسی داده ایجاد کنید و آنها را از سرویس Web API تفکیک نمایید.
فروشگاه اینترنتی شهر طلایی من
از این سیستم میتوان برای راه اندازی فروشگاه اینترنتی استفاده کرد ؛ در توسعه آن از Asp.net MVC 5 و EF Code first به صورت N-Tier استفاده شده است. چند ماه پیش برای یک هایپر مارکت در ارومیه این پروژه را کار میکردیم با دوستم آقای محمد شریفی ولی مشتری اواسط کار منصرف شد و ما هم دیگه دلسرد شدیم و به دلیل شروع کلاسهای دانشگاه از ادامه کار منصرف شدیم . پروژه کامل نشده ولی تا اونجایی که وقت یاری میداد از اکثر Best practiceهای موجود در سایت بهره بردم . هدف بنده و دوستم ادامه و تکمیل پروژه بود و الان هدف بنده از به اشتراک گذاری این پروژه، ادامه و تکمیل آن به کمک دوستان است.
لازم است اشاره کنم که پروژه IRIS دید خیلی خوبی به بنده داد .
نام کاربری :09146208938
کلمه عبور :09146208938
دمو :شهر طلایی من
برای راه اندازی از این مقاله استفاده کنید.
چطور باید برای یک پروژه دفترچه مشخصات فنی تهیه کرد؟
استاندارد خیر. ولی best practice هایی وجود داره.
و خیلی خیلی بستگی به سایزسازمان و تیم، ساختار تیم و روش توسعه، و عمق مستنداتی که تولید میکنید داره، مثال: گاهی یک تیم بزرگ تصمیم میگیره از ابزار wiki موجود در Gitlab, GitHub, Azure DevOps استفاده کنه (همونجایی که سورس کد قرار داره، ولی در بخش ویکی) یا در کانفلوئنس (اگر داشته باشن)
گاهی در فایل README.md در root پروژه به شکل قراردادی مشخصات ذکر میشه.
گاهی فایلهای markdown در پوشه docs در سورسکد
گاهی شما نیاز به معرفی REST API دارید که OpenAPI Spec کمک میکنه
گاهی دیاگرام هم نیاز دارید برای یک سیستم بزرگتر
گاهی فقط وابستگیها مثل پکیجها براتون مهمه، که فایل .packages.props کمک میاد
تجربه شخصی من، نسبت به هر تیم و شرکتی متناسب با همون تصمیم گرفتم (از فایل README.MD تا مدخل در ویکی که بعد از مدتی به چندصد صفحه تبدیل شده که چندین نفر به تدریج کاملش کردند)
<ItemGroup> <Content Include="wwwroot\**" /> </ItemGroup>
customElements.define("x-component", class extends HTMLElement { constructor() { super(); console.log('constructed!'); } connectedCallback() { console.log('connected!'); } disconnectedCallback() { console.log('disconnected!'); } adoptedCallback() { console.log('adopted!'); } attributeChangedCallback(name, oldValue, newValue) { console.log('attirbuteChanged!', name, oldValue, newValue); } static get observedAttributes() { return ['checked','demo','label']; } });
// "tabs" is not a valid custom element name document.createElement('tabs') instanceof HTMLUnknownElement === true //true // "x-tabs" is a valid custom element name document.createElement('x-tabs') instanceof HTMLElement === true //true
customElements.whenDefined('x-component').then(() => { console.log('x-component defined'); });
<share-buttons> <social-button type="twitter"><a href="...">Twitter</a></social-button> <social-button type="fb"><a href="...">Facebook</a></social-button> <social-button type="plus"><a href="...">G+</a></social-button> </share-buttons> // Fetch all the children of <share-buttons> that are not defined yet. let undefinedButtons = buttons.querySelectorAll(':not(:defined)'); let promises = [...undefinedButtons].map(socialButton => { return customElements.whenDefined(socialButton.localName); )); // Wait for all the social-buttons to be upgraded. Promise.all(promises).then(() => { // All social-button children are ready. });
customeElements.define('x-component',...) let XComponent = customElements.get('x-component'); document.body.appendChild(new XComponent())
const el = document.createElement('x-component'); document.body.appendChild(el); // connectedCallback() called el.remove(); // disconnectedCallback() document.body.appendChild(el); // connectedCallback() called again
const iframe = document.querySelector('iframe'); const iframeImages = iframe.contentDocument.querySelectorAll('img'); const newParent = document.getElementById('images'); iframeImages.forEach(function(imgEl) { newParent.appendChild(document.adoptNode(imgEl)); });
Methods
class XComponent extends HTMLElement { constructor() { super(); } doSomething(){ console.log('doSomething'); } }
let component = document.querySelector('x-component'); component.doSomething();
Attributes & Properties
div.id = 'id-value'; div.hidden = true;
<div id="id-value" hidden>
class XComponent extends HTMLElement { constructor() { super(); } connectedCallback() { this._render(); } get disabled() { return this.hasAttribute('disabled'); } set disabled(val) { // Reflect the value of `disabled` as an attribute. if (val) { this.setAttribute('disabled', ''); } else { this.removeAttribute('disabled'); } this._render(); } _render() { //... } }
attributeChangedCallback(name, oldValue, newValue) { switch (name) { case 'checked': // Note the attributeChangedCallback is only handling the *side effects* // of setting the attribute. this.setAttribute('aria-disabled', !!newValue); break; ... }
attributeChangedCallback(name, oldValue, newValue) { // When the component is disabled, update keyboard/screen reader behavior. if (this.disabled) { this.setAttribute('tabindex', '-1'); this.setAttribute('aria-disabled', 'true'); } else { this.setAttribute('tabindex', '0'); this.setAttribute('aria-disabled', 'false'); } // TODO: also react to the other attribute changing. }
constructor() { super(); this._data = []; } get data() { return _data; } set data(value) { if (this_data === value) return; this._data = value; this._render(); }
Lazy Properties
<x-component [disabled]="model.disabled"></x-component>
let el = document.querySelector('x-component'); el.disabled = true; customElements.define("x-component", class extends HTMLElement { constructor() { super(); } get disabled() { return this.hasAttribute('disabled'); } set disabled(val) { // Reflect the value of `disabled` as an attribute. if (val) { this.setAttribute('disabled', ''); } else { this.removeAttribute('disabled'); } this._render(); } });
let el = document.querySelector('x-component'); el.disabled = true; customElements.define("x-component", class extends HTMLElement { constructor() { super(); } connectedCallback() { this._upgradeProp('disabled'); } get disabled() { return this.hasAttribute('disabled'); } set disabled(val) { // Reflect the value of `disabled` as an attribute. if (val) { this.setAttribute('disabled', ''); } else { this.removeAttribute('disabled'); } this._render(); } _upgradeProp(prop) { if (this.hasOwnProperty(prop)) { let value = this[prop]; delete this[prop]; //delete instance property this[prop] = value; // set prototype property } } });
connectedCallback() { if (!this.hasAttribute('role')) this.setAttribute('role', 'checkbox'); if (!this.hasAttribute('tabindex')) this.setAttribute('tabindex', -1); //element is not reachable via sequential keyboard navigation, but could be focused }
گپ و گفتی با مهندسان طراح دات نت در مورد آینده این فریم ورک
معرفی DirectX 12 Ultimate
Add command 'listRemoteDockerProcess' and variable 'pickRemoteDockerProcess' (#4607, PR: #4617)
Ensure we only start one instance of OmniSharp server (PR: #4612)
Update OmniSharp version to 1.37.11
Include timing info in logged responses (PR: omnisharp-roslyn#2173)
Defend against null value in BuildErrorEventArgs (omnisharp-roslyn#2171, PR: omnisharp-roslyn#2172)
Updated to all the latest .NET SDKs (PR: omnisharp-roslyn#2166)
Add support for GoToDefinition on source-generated files (PR: omnisharp-roslyn#2170)
Add V2 version of GotoDefinitionService (PR: omnisharp-roslyn#2168)
avoid NRE when document is null (PR: omnisharp-roslyn#2163))