class Person : IPerson { private string Name { get; } private string Surname { get; } private IContact PrimaryContact { get; set; } private IList<IContact> AllContacts { get; } public Person(string name, string surname, IContact primaryContact) { if (string.IsNullOrEmpty(name)) throw new ArgumentException(nameof(name)); if (string.IsNullOrEmpty(surname)) throw new ArgumentException(nameof(surname)); this.Name = name; this.Surname = surname; this.AllContacts = new List<IContact>(); this.SetPrimaryContact(primaryContact); } public void SetPrimaryContact(IContact contact) { this.AddContact(contact); this.PrimaryContact = contact; } public void AddContact(IContact contact) { if (contact == null) throw new ArgumentNullException(nameof(contact)); this.AllContacts.Add(contact); } }
interface IContact { } class PhoneNumber : IContact { private string AreaCode { get; } private string Number { get; } public PhoneNumber(string areaCode, string number) { if (string.IsNullOrEmpty(areaCode)) throw new ArgumentException(nameof(areaCode)); if (string.IsNullOrEmpty(number)) throw new ArgumentException(nameof(number)); this.AreaCode = areaCode; this.Number = number; } } class EmailAddress : IContact { private string Address { get; } public EmailAddress(string address) { if (string.IsNullOrEmpty(address)) throw new ArgumentException(nameof(address)); this.Address = address; } }
طراحی PersonBuilder :
interface IPerson { void SetPrimaryContact(IContact primaryContact); void AddContact(IContact contact); } interface IPersonBuilder { void SetName(string name); void SetSurname(string surname); void SetPrimaryContact(IContact primaryContact); void AddContact(IContact contact); IPerson Build(); }
class PersonBuilder: IPersonBuilder { private string Name { get; set; } private string Surname { get; set; } private IContact PrimaryContact { get; set; } private IList<IContact> OtherContacts { get; } = new List<IContact>(); public void SetName(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentException(nameof(name)); this.Name = name; } public void SetSurname(string surname) { if (string.IsNullOrEmpty(surname)) throw new ArgumentException(nameof(surname)); this.Surname = surname; } public void SetPrimaryContact(IContact primaryContact) { if (primaryContact == null) throw new ArgumentNullException(nameof(primaryContact)); this.PrimaryContact = primaryContact; } public void AddContact(IContact contact) { if (contact == null) throw new ArgumentNullException(nameof(contact)); this.OtherContacts.Add(contact); } public IPerson Build() { IPerson person = new Person(this.Name, this.Surname, this.PrimaryContact); foreach (IContact contact in this.OtherContacts) person.AddContact(contact); return person; } }
راه حل: استفاده از Interface Segregation principle در PersonBuilder :
class PersonBuilder { private PersonBuilder() { } public static IExpectSurnamePersonBuilder WithName(string name) { ... } }
پس به ازای هر کدام از عملیاتها، یک اینترفیس خواهیم داشت:
interface IExpectSurnamePersonBuilder { IExpectPrimaryContactPersonBuilder WithSurname(string surname); } interface IExpectPrimaryContactPersonBuilder { IExpectOtherContactsPersonBuilder WithPrimaryContact(IContact contact); } interface IExpectOtherContactsPersonBuilder { IExpectOtherContactsPersonBuilder WithOtherContact(IContact contact); IPersonBuilder WithNoMoreContacts(); } interface IPersonBuilder { IPerson Build(); }
class PersonBuilder : IExpectSurnamePersonBuilder, IExpectPrimaryContactPersonBuilder, IExpectOtherContactsPersonBuilder, IPersonBuilder { private string Name { get; } private string Surname { get; set; } private IContact PrimaryContact { get; set; } private Person Person { get; set; } private PersonBuilder(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentException(nameof(name)); this.Name = name; } public static IExpectSurnamePersonBuilder WithName(string name) { return new PersonBuilder(name); } public IExpectPrimaryContactPersonBuilder WithSurname(string surname) { if (string.IsNullOrEmpty(surname)) throw new ArgumentException(nameof(surname)); this.Surname = surname; return this; } public IExpectOtherContactsPersonBuilder WithPrimaryContact(IContact contact) { if (contact == null) throw new ArgumentNullException(nameof(contact)); this.Person = new Person(this.Name, this.Surname, contact); return this; } public IExpectOtherContactsPersonBuilder WithOtherContact(IContact contact) { if (contact == null) throw new ArgumentNullException(nameof(contact)); this.Person.AddContact(contact); return this; } public IPersonBuilder WithNoMoreContacts() { return this; } public IPerson Build() { return this.Person; } }
IPerson person = PersonBuilder .WithName("Ali") .WithSurname("Karimi") .WithPrimaryContact(new EmailAddress("admin@gmail.com")) .WithOtherContact(new EmailAddress("Test1@work.com")) .WithOtherContact(new EmailAddress("Test2@home.com")) .WithNoMoreContacts() .Build();
چکیده:
عرضه Visual Studio 2013 Update 2 RTM
امکان ساخت قالب برای پروژههای NET Core.
خطا در MvcApplication
اضافه کردن دکمهی More info به صفحهی About و مدیریت کلیک بر روی آن
فایل Scripts\Templates\about.hbs را گشوده و سپس محتوای فعلی آن را به نحو ذیل تکمیل کنید:
<h2>About Ember Blog</h2> <p>Bla bla bla!</p> <button class="btn btn-primary" {{action 'showRealName' }}>more info</button>
به همین جهت فایل جدید Scripts\Controllers\about.js را در پوشهی کنترلرهای سمت کاربر اضافه کنید (نام آن با نام مسیریابی یکی است)؛ با این محتوا:
Blogger.AboutController = Ember.Controller.extend({ actions: { showRealName: function () { alert("You clicked at showRealName of AboutController."); } } });
در ادامه این خاصیت را با تهیه یک زیرکلاس از کلاس پایه Controller تهیه شده توسط ember.js مقدار دهی میکنیم. به این ترتیب به کلیه امکانات این کلاس پایه دسترسی خواهیم داشت؛ به علاوه میتوان ویژگیهای سفارشی را نیز به آن افزود. برای مثال در اینجا در قسمت actions آن، دقیقا مطابق نام اکشنی که در فایل about.hbs تعریف کردهایم، یک متد جدید اضافه شدهاست.
پس از تعریف کنترلر about.js نیاز است مدخل متناظر با آنرا به فایل index.html برنامه نیز در انتهای تعاریف موجود، اضافه کرد:
<script src="Scripts/Controllers/about.js" type="text/javascript"></script>
اکنون یکبار برنامه را اجرا کرده و در صفحهی about بر روی دکمهی more info کلیک کنید.
اضافه کردن دکمهی ارسال پیام خصوصی به صفحهی Contact و مدیریت کلیک بر روی آن
در ادامه به قالب فعلی Scripts\Templates\contact.hbs یک دکمه را جهت ارسال پیام خصوصی اضافه میکنیم.
<h1>Contact</h1> <div class="row"> <div class="col-md-6"> <p> Want to get in touch? <ul> <li>{{#link-to 'phone'}}Phone{{/link-to}}</li> <li>{{#link-to 'email'}}Email{{/link-to}}</li> </ul> </p> <p> Or, click here to send a secret message: </p> <button class="btn btn-primary" {{action 'sendMessage' }}>Send message</button> </div> <div class="col-md-6"> {{outlet}} </div> </div>
Blogger.ContactController = Ember.Controller.extend({ actions: { sendMessage: function () { var message = prompt('Type your message here:'); } } });
<script src="Scripts/Controllers/contact.js" type="text/javascript"></script>
نمایش تصویری تعاملی در صفحهی about
تا اینجا با نحوهی تعریف اکشنها در قالبها و مدیریت آنها توسط کنترلرهای متناظر آشنا شدیم. در ادامه قصد داریم با اصول binding اطلاعات در ember.js آشنا شویم. برای مثال فرض کنید میخواهیم دکمهای را در صفحهی about قرار داده و با کلیک بر روی آن، لوگوی ember.js را که به صورت یک تصویر مخفی در صفحه قرار دارد، نمایان کنیم. برای اینکار نیاز است خاصیتی را در کنترلر متناظر، تعریف کرده و سپس آنرا به template جاری bind کرد.
برای این منظور فایل Scripts\Templates\about.hbs را گشوده و تعاریف موجود آنرا به نحو ذیل تکمیل کنید:
<h2>About Ember Blog</h2> <p>Bla bla bla!</p> <button class="btn btn-primary" {{action 'showRealName' }}>more info</button> {{#if isAuthorShowing}} <button class="btn btn-warning" {{action 'hideAuthor' }}>Hide Image</button> <p><img src="Content/images/ember-productivity-sm.png"></p> {{else}} <button class="btn btn-info" {{action 'showAuthor' }}>Show Image</button> {{/if}}
کنترلر about (فایل Scripts\Controllers\about.js) جهت مدیریت این خاصیت جدید، به همراه دو اکشن تعریف شده، اینبار به نحو ذیل تغییر خواهد یافت:
Blogger.AboutController = Ember.Controller.extend({ isAuthorShowing: false, actions: { showRealName: function () { alert("You clicked at showRealName of AboutController."); }, showAuthor: function () { this.set('isAuthorShowing', true); }, hideAuthor: function () { this.set('isAuthorShowing', false); } } });
سپس در دو متد showAuthor و hideAuthor که به اکشنهای دو دکمهی جدید تعریف شده در قالب about متصل خواهند شد، نحوهی تغییر مقدار خاصیت isAuthorShowing را توسط متد set ملاحظه میکنید.
این قسمت مهمترین تفاوت ember.js با jQuery است. در jQuery مستقیما المانهای صفحه در همانجا تغییر داده میشوند. در ember.js منطق مدیریت کنندهی رابط کاربری و کدهای قالب متناظر با آن از هم جدا شدهاند تا بتوان یک برنامهی بزرگ را بهتر مدیریت کرد. همچنین در اینجا مشخص است که هر قسمت و هر فایل، چه ارتباطی با سایر اجزای تعریف شده دارد و چگونه به هم متصل شدهاند و اینبار شاهد انبوهی از کدهای جاوا اسکریپتی مخلوط بین المانهای HTML صفحه نیستیم.
نمایش پیامی به کاربر پس از ارسال پیام خصوصی در صفحهی تماس با ما
قصد داریم ویژگی مشابهی را به صفحهی contact نیز اضافه کنیم. اگر کاربر بر روی دکمهی ارسال پیام کلیک کرد، پیام تشکری به همراه عددی ویژه به او نمایش خواهیم داد.
برای اینکار قالب Scripts\Templates\contact.hbs را به نحو ذیل تکمیل کنید:
<h1>Contact</h1> <div class="row"> <div class="col-md-6"> <p> Want to get in touch? <ul> <li>{{#link-to 'phone'}}Phone{{/link-to}}</li> <li>{{#link-to 'email'}}Email{{/link-to}}</li> </ul> </p> {{#if messageSent}} <p> Thank you. Your message has been sent. Your confirmation number is {{confirmationNumber}}. </p> {{else}} <p> Or, click here to send a secret message: </p> <button class="btn btn-primary" {{action 'sendMessage' }}>Send message</button> {{/if}} </div> <div class="col-md-6"> {{outlet}} </div> </div>
برای تعریف منطق مرتبط با این خواص، به کنترلر contact واقع در فایل Scripts\Controllers\contact.js مراجعه کرده و آنرا به نحو ذیل تغییر میدهیم:
Blogger.ContactController = Ember.Controller.extend({ messageSent: false, actions: { sendMessage: function () { var message = prompt('Type your message here:'); if (message) { this.set('confirmationNumber', Math.round(Math.random() * 100000)); this.set('messageSent', true); } } } });
بنابراین به صورت خلاصه، کار کنترلر، مدیریت منطق نمایشی برنامه است و برای اینکار حداقل دو مکانیزم را ارائه میدهد: اکشنها و خواص. اکشنها بیانگر نوعی رفتار هستند؛ برای مثال نمایش یک popup و یا تغییر مقدار یک خاصیت. مقدار خواص را میتوان مستقیما در صفحه نمایش داد و یا از آنها جهت پردازش عبارات شرطی و نمایش قسمت خاصی از قالب جاری نیز میتوان کمک گرفت.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید:
EmberJS03_02.zip
روشهای اصلاح این کد بد بو
- لیست محصولات فروخته شده
- درصد کمیسیون خام
- تاریخ فروش
- شعبه فروش
- نوع پرداخت
public class Salesman { public void Method1() { return; } public void Method2() { return; } public void Method3() { return; } public decimal CalculateCommission(dynamic products, dynamic commissionRate, dynamic saleDate, dynamic branch, dynamic paymentType) { return decimal.MaxValue; } }
public class SalesmanV2 { public IEnumerable<dynamic> Products { get; set; } public dynamic CommisionRate { get; set; } public dynamic SaleDate { get; set; } public dynamic Branch { get; set; } public dynamic PaymentType { get; set; } public void Method1() { return; } public void Method2() { return; } public void Method3() { return; } public decimal CalculateCommission() { return decimal.MaxValue; } }
public class SalesmanV3 { public void Method1() { return; } public void Method2() { return; } public void Method3() { return; } }
public class CommissionCalculator { private IEnumerable<dynamic> _products; private dynamic _commisionRate; private dynamic _saleDate; private dynamic _branch; private dynamic _paymentType; public CommissionCalculator(IEnumerable<dynamic> products, dynamic commisionRate, dynamic saleDate, dynamic branch, dynamic paymentType) { _products = products; _commisionRate = commisionRate; _saleDate = saleDate; _branch = branch; _paymentType = paymentType; } }
جمع بندی
[RequireHttps] public class AccountController : Controller { public IActionResult Login() { return Content("Login Page"); } }
$ openssl genrsa -out key.pem 2048 $ openssl req -new -sha256 -key key.pem -out csr.csr $ openssl req -x509 -sha256 -days 365 -key key.pem -in csr.csr -out certificate.pem openssl pkcs12 -export -out localhost.pfx -inkey key.pem -in certificate.pem
$ dotnet add package Microsoft.AspNetCore.Server.Kestrel.Https
namespace testingSSL { public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseKestrel(options => { options.Listen(IPAddress.Any, 8080); options.Listen(IPAddress.Any, 443, listenOptions => listenOptions.UseHttps("localhost.pfx", "qwe123456")); }) .UseStartup<Startup>() .Build(); } }
البته تا اینجا، هدف بررسی ویژگی RequireHttps بود؛ طبیعتاً به SSL در حین Development نیازی نخواهید داشت. در نتیجه میتوانیم به صورت Global تمامی کنترلرها را در زمان Production به ویژگی گفته شده مزین کنیم:
private readonly IHostingEnvironment _env; public Startup(IConfiguration configuration, IHostingEnvironment env) { Configuration = configuration; _env = env; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); if (!_env.IsDevelopment()) services.Configure<MvcOptions>(o => o.Filters.Add(new RequireHttpsAttribute())); }
(Http Strict Transport Security (HSTS
هدایت کردن خودکار درخواست از حالت HTTP به HTTPS، توسط خیلی از سایتها انجام میشود:
البته این روش بهتر از استفاده نکردن از SSL است؛ اما در نظر داشته باشید که همیشه اولین درخواست به صورت رمزنگاری نشده ارسال خواهد شد. فرض کنید در یک محیط پابلیک از طریق WiFi به اینترنت متصل شدهایم. شخصی (هکر) که بر روی مودم کنترل دارد، طوری WiFi را پیکربندی کردهاست که به جای آدرس اصلی که در تصویر مشاهده میکنید، یک نسخه جعلی از سایت باز شود؛ به طوریکه URL همانند URL اصلی باشد. در اینحالت کاربر به جای اینکه نامکاربری و کلمهعبور را وارد سایت اصلی کند، آن را درون سایت جعلی وارد خواهد کرد. برای حل این مشکل میتوانیم وبسایتمان را طوری تنظیم کنیم که هدر Strict-Transport-Security را به هدر اولین responseی که توسط مرورگر دریافت میشود اضافه کند:
Strict-Transport-Security: max-age=31536000
بنابراین مرورگر وبسایت را درون یک لیست internal به مدت یکسال (مقدار max-age) نگهداری خواهد کرد؛ در طول این زمان به هیچ درخواست ناامنی اجازه داده نخواهد شد. به این قابلیت HSTS گفته میشود. البته ASP.NET Core به صورت توکار روشی را جهت اضافه کردن این هدر ارائه نداده است؛ اما میتوانیم خودمان یک Middleware سفارشی را به pipeline اضافه کنیم تا اینکار را برایمان انجام دهد:
namespace testingSSL.Middleware { public class HstsMiddleware { private readonly RequestDelegate _next; public HstsMiddleware(RequestDelegate next) { _next = next; } public Task Invoke(HttpContext context) { if (!context.Request.IsHttps) return _next(context); if (IsLocalhost(context)) return _next(context); context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000"); return _next(context); } private bool IsLocalhost(HttpContext context) { return string.Equals(context.Request.Host.Host, "localhost", StringComparison.OrdinalIgnoreCase); } } }
یا اینکه میتوانیم از کتابخانه NWebSec استفاده کنیم:
$ dotnet add package NWebsec.AspNetCore.Middleware
برای استفاده از آن نیز خواهیم داشت:
app.UseHsts(h => h.MaxAge(days: 365));
اما هنوز یک مشکل وجود دارد؛ هنوز مشکل اولین درخواست را برطرف نکردهایم. زیرا مرورگر برای دریافت این هدر نیاز به مراجعه به سایت دارد. برای حل این مشکل میتوانید آدرس وبسایت خود را در سایت hstspreload ثبت کنید، سپس متد PreLoad را به کد فوق اضافه کنید:
app.UseHsts(h => h.MaxAge(days: 365).Preload());
در اینحالت حتی اگر کسی به وبسایت شما مراجعه نکند، مرورگر میداند که باید از HTTPS استفاده کند. زیرا این لیست به صورت توکار درون مرورگر تعبیه شدهاست. بنابراین در اینحالت مطمئن خواهیم شد که تمامی connectionها به سایتمان امن میباشند.
دریافت کدهای مطلب جاری (+)