ردیابی تغییرات در سمت کلاینت توسط Web API
فرض کنید میخواهیم از سرویسهای REST-based برای انجام عملیات CRUD روی یک Object graph استفاده کنیم. همچنین میخواهیم رویکردی در سمت کلاینت برای بروز رسانی کلاس موجودیتها پیاده سازی کنیم که قابل استفاده مجدد (reusable) باشد. علاوه بر این دسترسی دادهها توسط مدل Code-First انجام میشود.
در مثال جاری یک اپلیکیشن کلاینت (برنامه کنسول) خواهیم داشت که سرویسهای ارائه شده توسط پروژه Web API را فراخوانی میکند. هر پروژه در یک Solution مجزا قرار دارد، با این کار یک محیط n-Tier را شبیه سازی میکنیم.
مدل زیر را در نظر بگیرید.
همانطور که میبینید مدل مثال جاری مشتریان و شماره تماس آنها را ارائه میکند. میخواهیم مدلها و کد دسترسی به دادهها را در یک سرویس Web API پیاده سازی کنیم تا هر کلاینتی که به HTTP دسترسی دارد بتواند از آن استفاده کند. برای ساخت سرویس مذکور مراحل زیر را دنبال کنید.
- در ویژوال استودیو پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب پروژه را Web API انتخاب کنید. نام پروژه را به Recipe4.Service تغییر دهید.
- کنترلر جدیدی با نام CustomerController به پروژه اضافه کنید.
- کلاسی با نام BaseEntity ایجاد کنید و کد آن را مطابق لیست زیر تغییر دهید. تمام موجودیتها از این کلاس پایه مشتق خواهند شد که خاصیتی بنام TrackingState را به آنها اضافه میکند. کلاینتها هنگام ویرایش آبجکت موجودیتها باید این فیلد را مقدار دهی کنند. همانطور که میبینید این خاصیت از نوع TrackingState enum مشتق میشود. توجه داشته باشید که این خاصیت در دیتابیس ذخیره نخواهد شد. با پیاده سازی enum وضعیت ردیابی موجودیتها بدین روش، وابستگیهای EF را برای کلاینت از بین میبریم. اگر قرار بود وضعیت ردیابی را مستقیما از EF به کلاینت پاس دهیم وابستگیهای بخصوصی معرفی میشدند. کلاس DbContext اپلیکیشن در متد OnModelCreating به EF دستور میدهد که خاصیت TrackingState را به جدول موجودیت نگاشت نکند.
public abstract class BaseEntity { protected BaseEntity() { TrackingState = TrackingState.Nochange; } public TrackingState TrackingState { get; set; } } public enum TrackingState { Nochange, Add, Update, Remove, }
- کلاسهای موجودیت Customer و PhoneNumber را ایجاد کنید و کد آنها را مطابق لیست زیر تغییر دهید.
public class Customer : BaseEntity { public int CustomerId { get; set; } public string Name { get; set; } public string Company { get; set; } public virtual ICollection<Phone> Phones { get; set; } } public class Phone : BaseEntity { public int PhoneId { get; set; } public string Number { get; set; } public string PhoneType { get; set; } public int CustomerId { get; set; } public virtual Customer Customer { get; set; } }
- با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
- کلاسی با نام Recipe4Context ایجاد کنید و کد آن را مطابق لیست زیر تغییر دهید. در این کلاس از یکی از قابلیتهای جدید EF 6 بنام "Configuring Unmapped Base Types" استفاده کرده ایم. با استفاده از این قابلیت جدید هر موجودیت را طوری پیکربندی میکنیم که خاصیت TrackingState را نادیده بگیرند. برای اطلاعات بیشتر درباره این قابلیت EF 6 به این لینک مراجعه کنید.
public class Recipe4Context : DbContext { public Recipe4Context() : base("Recipe4ConnectionString") { } public DbSet<Customer> Customers { get; set; } public DbSet<Phone> Phones { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { // Do not persist TrackingState property to data store // This property is used internally to track state of // disconnected entities across service boundaries. // Leverage the Custom Code First Conventions features from Entity Framework 6. // Define a convention that performs a configuration for every entity // that derives from a base entity class. modelBuilder.Types<BaseEntity>().Configure(x => x.Ignore(y => y.TrackingState)); modelBuilder.Entity<Customer>().ToTable("Customers"); modelBuilder.Entity<Phone>().ToTable("Phones"); } }
- فایل Web.config پروژه را باز کنید و رشته اتصال زیر را به قسمت ConnectionStrings اضافه نمایید.
<connectionStrings> <add name="Recipe4ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
- فایل Global.asax را باز کنید و کد زیر را به متد Application_Start اضافه نمایید. این کد بررسی Entity Framework Model Compatibility را غیرفعال میکند و به JSON serializer دستور میدهد که self-referencing loop خواص پیمایشی را نادیده بگیرد. این حلقه بدلیل رابطه bidirectional بین موجودیتهای Customer و PhoneNumber بوجود میآید.
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; ... }
- کلاسی با نام EntityStateFactory بسازید و کد آن را مطابق لیست زیر تغییر دهید. این کلاس مقدار خاصیت TrackingState که به کلاینتها ارائه میشود را به مقادیر متناظر کامپوننتهای ردیابی EF تبدیل میکند.
public static EntityState Set(TrackingState trackingState) { switch (trackingState) { case TrackingState.Add: return EntityState.Added; case TrackingState.Update: return EntityState.Modified; case TrackingState.Remove: return EntityState.Deleted; default: return EntityState.Unchanged; } }
- در آخر کد کنترلر CustomerController را مطابق لیست زیر بروز رسانی کنید.
public class CustomerController : ApiController { // GET api/customer public IEnumerable<Customer> Get() { using (var context = new Recipe4Context()) { return context.Customers.Include(x => x.Phones).ToList(); } } // GET api/customer/5 public Customer Get(int id) { using (var context = new Recipe4Context()) { return context.Customers.Include(x => x.Phones).FirstOrDefault(x => x.CustomerId == id); } } [ActionName("Update")] public HttpResponseMessage UpdateCustomer(Customer customer) { using (var context = new Recipe4Context()) { // Add object graph to context setting default state of 'Added'. // Adding parent to context automatically attaches entire graph // (parent and child entities) to context and sets state to 'Added' // for all entities. context.Customers.Add(customer); foreach (var entry in context.ChangeTracker.Entries<BaseEntity>()) { entry.State = EntityStateFactory.Set(entry.Entity.TrackingState); if (entry.State == EntityState.Modified) { // For entity updates, we fetch a current copy of the entity // from the database and assign the values to the orginal values // property from the Entry object. OriginalValues wrap a dictionary // that represents the values of the entity before applying changes. // The Entity Framework change tracker will detect // differences between the current and original values and mark // each property and the entity as modified. Start by setting // the state for the entity as 'Unchanged'. entry.State = EntityState.Unchanged; var databaseValues = entry.GetDatabaseValues(); entry.OriginalValues.SetValues(databaseValues); } } context.SaveChanges(); } return Request.CreateResponse(HttpStatusCode.OK, customer); } [HttpDelete] [ActionName("Cleanup")] public HttpResponseMessage Cleanup() { using (var context = new Recipe4Context()) { context.Database.ExecuteSqlCommand("delete from phones"); context.Database.ExecuteSqlCommand("delete from customers"); return Request.CreateResponse(HttpStatusCode.OK); } } }
- در ویژوال استودیو پروژه جدیدی از نوع Console Application بسازید و نام آن را به Recipe4.Client تغییر دهید.
- فایل program.cs را باز کنید و کد آن را مطابق لیست زیر تغییر دهید.
internal class Program { private HttpClient _client; private Customer _bush, _obama; private Phone _whiteHousePhone, _bushMobilePhone, _obamaMobilePhone; 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 completes await program.CleanupAsync(); program.CreateFirstCustomer(); // do not proceed until customer is added await program.AddCustomerAsync(); program.CreateSecondCustomer(); // do not proceed until customer is added await program.AddSecondCustomerAsync(); // do not proceed until customer is removed await program.RemoveFirstCustomerAsync(); // do not proceed until customers are fetched await program.FetchCustomersAsync(); } private void ServiceSetup() { // set up infrastructure for Web API call _client = new HttpClient { BaseAddress = new Uri("http://localhost:62799/") }; // 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 the cleanup method from the service _response = await _client.DeleteAsync("api/customer/cleanup/"); } private void CreateFirstCustomer() { // create customer #1 and two phone numbers _bush = new Customer { Name = "George Bush", Company = "Ex President", // set tracking state to 'Add' to generate a SQL Insert statement TrackingState = TrackingState.Add, }; _whiteHousePhone = new Phone { Number = "212 222-2222", PhoneType = "White House Red Phone", // set tracking state to 'Add' to generate a SQL Insert statement TrackingState = TrackingState.Add, }; _bushMobilePhone = new Phone { Number = "212 333-3333", PhoneType = "Bush Mobile Phone", // set tracking state to 'Add' to generate a SQL Insert statement TrackingState = TrackingState.Add, }; _bush.Phones.Add(_whiteHousePhone); _bush.Phones.Add(_bushMobilePhone); } private async Task AddCustomerAsync() { // construct call to invoke UpdateCustomer action method in Web API service _response = await _client.PostAsync("api/customer/updatecustomer/", _bush, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { // capture newly created customer entity from service, which will include // database-generated Ids for all entities _bush = await _response.Content.ReadAsAsync<Customer>(); _whiteHousePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _bush.CustomerId); _bushMobilePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _bush.CustomerId); Console.WriteLine("Successfully created Customer {0} and {1} Phone Numbers(s)", _bush.Name, _bush.Phones.Count); foreach (var phoneType in _bush.Phones) { Console.WriteLine("Added Phone Type: {0}", phoneType.PhoneType); } } else Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase); } private void CreateSecondCustomer() { // create customer #2 and phone numbers _obama = new Customer { Name = "Barack Obama", Company = "President", // set tracking state to 'Add' to generate a SQL Insert statement TrackingState = TrackingState.Add, }; _obamaMobilePhone = new Phone { Number = "212 444-4444", PhoneType = "Obama Mobile Phone", // set tracking state to 'Add' to generate a SQL Insert statement TrackingState = TrackingState.Add, }; // set tracking state to 'Modifed' to generate a SQL Update statement _whiteHousePhone.TrackingState = TrackingState.Update; _obama.Phones.Add(_obamaMobilePhone); _obama.Phones.Add(_whiteHousePhone); } private async Task AddSecondCustomerAsync() { // construct call to invoke UpdateCustomer action method in Web API service _response = await _client.PostAsync("api/customer/updatecustomer/", _obama, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { // capture newly created customer entity from service, which will include // database-generated Ids for all entities _obama = await _response.Content.ReadAsAsync<Customer>(); _whiteHousePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _obama.CustomerId); _bushMobilePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _obama.CustomerId); Console.WriteLine("Successfully created Customer {0} and {1} Phone Numbers(s)", _obama.Name, _obama.Phones.Count); foreach (var phoneType in _obama.Phones) { Console.WriteLine("Added Phone Type: {0}", phoneType.PhoneType); } } else Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase); } private async Task RemoveFirstCustomerAsync() { // remove George Bush from underlying data store. // first, fetch George Bush entity, demonstrating a call to the // get action method on the service while passing a parameter var query = "api/customer/" + _bush.CustomerId; _response = _client.GetAsync(query).Result; if (_response.IsSuccessStatusCode) { _bush = await _response.Content.ReadAsAsync<Customer>(); // set tracking state to 'Remove' to generate a SQL Delete statement _bush.TrackingState = TrackingState.Remove; // must also remove bush's mobile number -- must delete child before removing parent foreach (var phoneType in _bush.Phones) { // set tracking state to 'Remove' to generate a SQL Delete statement phoneType.TrackingState = TrackingState.Remove; } // construct call to remove Bush from underlying database table _response = await _client.PostAsync("api/customer/updatecustomer/", _bush, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { Console.WriteLine("Removed {0} from database", _bush.Name); foreach (var phoneType in _bush.Phones) { Console.WriteLine("Remove {0} from data store", phoneType.PhoneType); } } else Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase); } else { Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase); } } private async Task FetchCustomersAsync() { // finally, return remaining customers from underlying data store _response = await _client.GetAsync("api/customer/"); if (_response.IsSuccessStatusCode) { var customers = await _response.Content.ReadAsAsync<IEnumerable<Customer>>(); foreach (var customer in customers) { Console.WriteLine("Customer {0} has {1} Phone Numbers(s)", customer.Name, customer.Phones.Count()); foreach (var phoneType in customer.Phones) { Console.WriteLine("Phone Type: {0}", phoneType.PhoneType); } } } else { Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase); } } }
- در آخر کلاسهای Customer, Phone و BaseEntity را به پروژه کلاینت اضافه کنید. چنین کدهایی بهتر است در لایه مجزایی قرار گیرند و بین لایههای مختلف اپلیکیشن به اشتراک گذاشته شوند.
اگر اپلیکیشن کلاینت را اجرا کنید با خروجی زیر مواجه خواهید شد.
شرح مثال جاری
با اجرای اپلیکیشن Web API شروع کنید. این اپلیکیشن یک MVC Web Controller دارد که پس از اجرا شما را به صفحه خانه هدایت میکند. در این مرحله سایت در حال اجرا است و سرویسها قابل دسترسی هستند.
سپس اپلیکیشن کنسول را باز کنید و روی خط اول کد فایل program.cs یک breakpoint قرار داده و آن را اجرا کنید. ابتدا آدرس سرویس را نگاشت میکنیم و از سرویس درخواست میکنیم که اطلاعات را با فرمت JSON بازگرداند.
سپس توسط متد DeleteAsync که روی آبجکت HttpClient تعریف شده است اکشن متد Cleanup را روی سرویس فراخوانی میکنیم. این فراخوانی تمام دادههای پیشین را حذف میکند.
در قدم بعدی یک مشتری بهمراه دو شماره تماس میسازیم. توجه کنید که برای هر موجودیت مشخصا خاصیت TrackingState را مقدار دهی میکنیم تا کامپوننتهای Change-tracking در EF عملیات لازم SQL برای هر موجودیت را تولید کنند.
سپس توسط متد PostAsync که روی آبجکت HttpClient تعریف شده اکشن متد UpdateCustomer را روی سرویس فراخوانی میکنیم. اگر به این اکشن متد یک breakpoint اضافه کنید خواهید دید که موجودیت مشتری را بعنوان یک پارامتر دریافت میکند و آن را به context جاری اضافه مینماید. با اضافه کردن موجودیت به کانتکست جاری کل object graph اضافه میشود و EF شروع به ردیابی تغییرات آن میکند. دقت کنید که آبجکت موجودیت باید Add شود و نه Attach.
قدم بعدی جالب است، هنگامی که از خاصیت DbChangeTracker استفاده میکنیم. این خاصیت روی آبجکت context تعریف شده و یک <IEnumerable<DbEntityEntry را با نام Entries ارائه میکند. در اینجا بسادگی نوع پایه EntityType را تنظیم میکنیم. این کار به ما اجازه میدهد که در تمام موجودیت هایی که از نوع BaseEntity هستند پیمایش کنیم. اگر بیاد داشته باشید این کلاس، کلاس پایه تمام موجودیتها است. در هر مرحله از پیمایش (iteration) با استفاده از کلاس EntityStateFactory مقدار خاصیت TrackingState را به مقدار متناظر در سیستم ردیابی EF تبدیل میکنیم. اگر کلاینت مقدار این فیلد را به Modified تنظیم کرده باشد پردازش بیشتری انجام میشود. ابتدا وضعیت موجودیت را از Modified به Unchanged تغییر میدهیم. سپس مقادیر اصلی را با فراخوانی متد GetDatabaseValues روی آبجکت Entry از دیتابیس دریافت میکنیم. فراخوانی این متد مقادیر موجود در دیتابیس را برای موجودیت جاری دریافت میکند. سپس مقادیر بدست آمده را به کلکسیون OriginalValues اختصاص میدهیم. پشت پرده، کامپوننتهای EF Change-tracking بصورت خودکار تفاوتهای مقادیر اصلی و مقادیر ارسالی را تشخیص میدهند و فیلدهای مربوطه را با وضعیت Modified علامت گذاری میکنند. فراخوانیهای بعدی متد SaveChanges تنها فیلدهایی که در سمت کلاینت تغییر کرده اند را بروز رسانی خواهد کرد و نه تمام خواص موجودیت را.
در اپلیکیشن کلاینت عملیات افزودن، بروز رسانی و حذف موجودیتها توسط مقداردهی خاصیت TrackingState را نمایش داده ایم.
متد UpdateCustomer در سرویس ما مقادیر TrackingState را به مقادیر متناظر EF تبدیل میکند و آبجکتها را به موتور change-tracking ارسال میکند که نهایتا منجر به تولید دستورات لازم SQL میشود.
نکته: در اپلیکیشنهای واقعی بهتر است کد دسترسی دادهها و مدلهای دامنه را به لایه مجزایی منتقل کنید. همچنین پیاده سازی فعلی change-tracking در سمت کلاینت میتواند توسعه داده شود تا با انواع جنریک کار کند. در این صورت از نوشتن مقادیر زیادی کد تکراری جلوگیری خواهید کرد و از یک پیاده سازی میتوانید برای تمام موجودیتها استفاده کنید.
تفاوت مجوز LGPL با GPL در چیست؟
A component licensed under LGPL can be used by closed source, proprietary software, both internally used and distributed, for free, with no effects on the software using the component. LGPL is not “contagious” in the same way as GPL, so it only affects the component under LGPL. As long as you’re only using official distributions of the component, it is free to use and free to redistribute. The only requirement is that you include a notice in your “about” page or similar that the component is used.
معرفی OpenJDK ساخت مایکروسافت
آغاز کار با git در ویژوال استدیو
In this episode, Robert is joined by Paul Litwin, who shows us how to get started with Git in under an hour. Git is a free, open source and quite popular distributed version control system designed to handle everything from small to very large projects with speed and efficiency. Starting with the command line and ending up in both VS Code and Visual Studio, Paul takes us on a tour of the how you can use Git to manage your source code.
در این مطلب با توجه به سؤالات زیادی که در مورد صرفا نحوهی اجرای بدون خطای آن وجود داشت، ریز مراحل آنرا بررسی میکنیم.
پیشنیازهای توسعهی برنامه
- با توجه به استفاده از ویژگیهای C# 6 در این پروژه، حتما نیاز است برای کار و اجرای آن از VS 2015 استفاده کنید.
- همچنین این پروژه از قابلیت «فایل استریم» SQL Server استفاده میکند. بنابراین نیاز است نگارش متناسبی از SQL Server را پیشتر نصب کرده باشید (هر نگارشی بالاتر از SQL Server 2005).
- اگر از ReSharper استفاده میکنید، به صورت موقت آنرا به حالت تعلیق درآورید (منوی tools، گرینهی options و انتخاب resharper و سپس suspend کردن آن). این مورد سرعت بازیابی بستههای نیوگت را به شدت افزایش میدهد.
بازیابی وابستگیهای نیوگت پروژه
مرسوم نیست چند 10 مگابایت وابستگیهای پروژه را به صورت فایلهای باینری، به مخزن کدها ارسال کرد. از این جهت که نیوگت بر اساس مداخل فایلهای packages.config، قابلیت بازیابی و نصب خودکار آنها را دارد. بنابراین ابتدا package manger console را باز کنید؛ از طریق منوی Tools -> NuGet Package Manager -> Package Manager Console
همانطور که در تصویر مشاهده میکنید، نیوگت تشخیص دادهاست که بستههایی برای نصب وجود دارند. بنابراین بر روی دکمهی restore کلیک کنید تا کار دریافت و نصب خودکار این بستهها از اینترنت شروع شود. البته اگر پیشتر این بستهها را در پروژههای دیگری نصب کرده باشید، نیوگت از کش موجود در سیستم استفاده خواهد کرد و برای دریافت آنها به اینترنت مراجعه نمیکند. ولی در هر حال اتصال به اینترنت ضروری است.
پس از پایان کار بازیابی بستهها، یکبار کل Solution را Build کنید تا مطمئن شوید که تمام بستههای مورد نیاز به درستی بازیابی و نصب شدهاند (Ctrl+Shift+B و یا همان منوی Build و انتخاب گزینهی Build Solution).
تنظیمات رشته اتصالی بانک اطلاعاتی برنامه
پس از Build موفق کل Solution در مرحلهی قبل، اکنون نوبت به برپایی تنظیمات بانک اطلاعاتی برنامه است. برای این منظور فایل web.config ذیل را باز کنید:
Decision\src\Decision.Web\Web.config
یک چنین تنظیمی را مشاهده میکنید:
<connectionStrings> <clear /> <add name="DefaultConnection" connectionString="Data Source=.\sqlexpress;Initial Catalog=DecisionDb;Integrated Security = true;MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" /> </connectionStrings>
<connectionStrings> <clear /> <add name="DefaultConnection" connectionString="Data Source=(local);Initial Catalog=DecisionDb;Integrated Security = true;MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" /> </connectionStrings>
در این حالت نیاز است بانک اطلاعاتی خالی DecisionDb را خودتان ایجاد کنید. علت آن به AutomaticMigrationsEnabled = false بر میگردد؛ که در ادامه توضیح داده شدهاست و همچنین وجود تنظیم ذیل در فایل Decision\src\Decision.Web\App_Start\ApplicationStart.cs
Database.SetInitializer<ApplicationDbContext>(null);
ایجاد بانک اطلاعاتی برنامه و تنظیمات آن
پس از آن، نوبت به ایجاد بانک اطلاعاتی برنامه است. چون این برنامه از EF Code first استفاده میکند، قادر است بانک اطلاعاتی ذکر شدهی در Initial Catalog فوق را به صورت خودکار ایجاد کند (با تمام جداول، روابط و تنظیمات آنها). این اطلاعات هم از پروژهی Decision.DataLayer و پوشهی Migrations آن تامین میشوند.
اگر به فایل Decision\src\Decision.DataLayer\Migrations\201602072159421_Initial.cs مراجعه کنید، یکسری تنظیمات دستی را هم علاوه بر کدهای خودکار EF، مشاهده خواهید کرد:
//. . . Sql("EXEC sp_configure filestream_access_level, 2"); Sql("RECONFIGURE", true); Sql("alter database DecisionDb Add FileGroup FileGroupApplicant contains FileStream", true); Sql("alter database DecisionDb add file ( name = 'ApplicantDocuements' , filename = 'C:\\FileStream\\ApplicantDocuements') to filegroup FileGroupApplicant", true); //. . .
بنابراین نیاز است در درایور C، پوشهی خالی FileStream از پیش تهیه شده باشد (نیازی به ایجاد پوشهی ApplicantDocuements نیست و این پوشه به صورت خودکار ایجاد میشود).
و در فایل Decision\src\Decision.DataLayer\Migrations\Configuration.cs مشخص شدهاست که AutomaticMigrationsEnabled = false. به این معنا که تنظیمات فوق به صورت خودکار به بانک اطلاعاتی اعمال نشده و باید چند دستور ذیل را به صورت دستی صادر کنیم:
الف) ابتدا package manager console را مجددا باز کنید و در اینجا default project را بر روی Decision.DataLayer قرار دهید. از این جهت که قرار است اطلاعات migration را از این پروژه دریافت کنیم:
در غیراینصورت پیام خطای No migrations configuration type was found in the assembly را دریافت خواهید کرد.
ب) سپس دستور ذیل را صادر کنید (با این فرض که بانک اطلاعاتی خالی DecisionDb ذکر شدهی در قسمت قبل را پیشتر ایجاد کردهاید):
PM> Update-Database -Verbose -ConnectionStringName "DefaultConnection" -StartUpProjectName "Decision.Web"
من در این حالت پیام خطای Update-Database : The term 'Update-Database' is not recognized as the name of a cmdlet را دریافت کردم.
راه حل: یکبار ویژوال استودیو را بسته و مجددا باز کنید تا کار نصب بستهها و بارگذاری تمام وابستگیهای آنها به درستی صورت گیرد. این خطا به این معنا است که هرچند NuGet کار نصب EF را انجام دادهاست، اما هنوز اسکریپتهای پاورشل آن که دستوراتی مانند Update-Database را اجرا میکنند، بارگذاری نشدهاند. راه حل آن بستن و اجرای مجدد ویژوال استودیو است.
پس از اجرای مجدد ویژوال استودیو و انتخاب default project صحیح (مطابق تصویر فوق)، مجددا دستور Update-Database فوق را صادر کنید (با پارامترهای ویژهی آن).
با صدور این دستور، پیام خطای ذیل را دریافت کردم:
The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework' registered in the application config file for the ADO.NET provider with invariant name 'System.Data.SqlClient' could not be loaded.
PM> Update-Package -Reinstall "EntityFramework" -ProjectName "Decision.DataLayer"
اینبار دستور update-database فوق (با پارامترهای ویژهی آن) بدون مشکل اجرا شد و بانک اطلاعاتی مربوطه تشکیل گردید.
اکنون برنامه قابل اجرا است و در این حالت است که میتوان دکمهی F5 را جهت اجرای برنامه فشرد. البته در این حالت بر روی پروژهی Decision.Web کلیک راست کرده و گزینهی set as startup project را نیز انتخاب کنید و سپس F5:
لطفا سؤالاتی را که مرتبط با «راه اندازی» این پروژه نیستند، در قسمت بازخوردهای اختصاصی آن مطرح کنید.
TypeScript 5.0 منتشر شد
This release brings many new features, while aiming to make TypeScript smaller, simpler, and faster. We’ve implemented the new decorators standard, added functionality to better support ESM projects in Node and bundlers, provided new ways for library authors to control generic inference, expanded our JSDoc functionality, simplified configuration, and made many other improvements.
No Authentication
اگر گزینه No Authentication را انتخاب کنید، پروژه ایجاد شده صفحاتی را برای ورود به سایت نخواهد ساخت. همچنین رابط کاربری ای برای نمایش کاربر فعلی، کلاسهای موجودیتها برای یک دیتابیس عضویت و رشتههای اتصالی نیز وجود نخواهند داشت.
- سیستم عضویت جدید بجای استفاده از ماژول ASP.NET Forms Authentication بر پایه OWIN نوشته شده است. این بدین معنا است که از یک مکانیزم احراز هویت واحد میتوانید در اپلیکیشنهای ASP.NET Web Forms, MVC, Web API و SignalR استفاده کنید.
- سیستم عضویت جدید توسط Entity Framework Code First مدیریت میشود و شامل تمامی کلاسهایی است که نماینده جداول و موجودیتها هستند. این بدین معنا است که روی الگوی دیتابیس کنترل کامل دارید. سفارشی سازی و تغییر اطلاعات کاربران و پروفایل هایشان بسیار سادهتر است، تنها لازم است برای اعمال تغییرات از Code First Migrations استفاده کنید.
- asp.net/identity
- Create an ASP.NET MVC 5 App with Facebook and Google OAuth2 and OpenID Sign-on
- Web API - External Authentication Services
- Adding External Logins to your ASP.NET application in Visual Studio 2013
اگر گزینه Organizational Accounts را انتخاب کنید پروژه ایجاد شده برای استفاده از (Windows Identity Foundation (WIF پیکربندی خواهد شد. این فریم ورک برای احراز هویت کاربران از (Windows Azure Active Directory (WAAD استفاده میکند که شامل Office 365 نیز میشود.
از این گزینه برای احراز هویت کاربرانی استفاده کنید که در قالب یک OWIN Tenant تعریف میشوند. برای مثال سایتی با نام Company.com داریم که برای کارمندان این سازمان از طریق company.onmicrosoft.com قابل دسترسی خواهد بود. نمیتوانید WAAD را طوری پیکربندی کنید که کاربران tenantهای دیگر نیز به اپلیکیشن شما دسترسی داشته باشند.
Domain
نام دامنهای در WAAD که میخواهید اپلیکیشن را برای آن پیکربندی کنید، مثلا company.onmicrosoft.com. اگر از custom domain ها استفاده میکنید مانند company.com بجای company.onmicrosoft.com میتوانید این اطلاعات را اینجا وارد کنید.
سطح دسترسی
اگر اپلیکیشن نیاز به کوئری گرفتن یا بروز رسانی اطلاعات پوشهها (directory information) را توسط Graph API دارد، از گزینههای Single Sign-On, Read Directory Data و یا Single Sign-On, Read and Write Directory Data استفاده کنید. در غیر اینصورت گزینه Single Sign-On را رها کنید. برای اطلاعات بیشتر به Application Access Levels و Using the Graph API to Query Windows Azure AD مراجعه کنید.
Application ID URI
بصورت پیش فرض، قالب پروژه یک شناسه application ID URI برای شما تولید میکند، که این کار با الحاق نام پروژه شما به نام دامنه WAAD صورت میگیرد. برای مثال، اگر نام پروژه Example باشد و نام دامنه contoso.onmicrosoft.com، شناسه خروجی https://contoso.onmicrosoft.com/Example میشود. اگر میخواهید بصورت دستی این فیلد را مقدار دهی کنید، گزینه More Options را انتخاب کنید. این شناسه باید با //:https شروع شود.
بصورت پیش فرض، اگر اپلیکیشنی که در WAAD تهیه و تدارک دیده شده است، شناسهای یکسان با شناسه موجود در پروژه Visual Studio داشته باشد، پروژه شما به اپلیکیشن موجود در WAAD متصل خواهد شد. اگر میخواهید تدارکات جدیدی ببینید تیک گزینه Overwrite the application entry if one with the same ID already exists را حذف کنید.
اگر تیک این گزینه حذف شده باشد، و ویژوال استودیو اپلیکیشنی با شناسهای یکسان را پیدا کند، عددی به آخر URI اضافه خواهد شد. مثلا فرض کنید نام پروژه Example است و اپلیکیشنی نیز با شناسه https://contoso.onmicrosoft.com/Example در WAAD وجود دارد. در این صورت اپلیکیشن جدیدی با شناسه ای مانند https://contoso.onmicrosoft.com/Example_ 20130619330903 ایجاد میشود.
تهیه و تدارک اپلیکیشن در WAAD
برای آنکه یک اپلیکیشن WAAD ایجاد کنید و یا پروژه را به یک اپلیکیشن موجود متصل کنید، ویژوال استودیو به اطلاعات ورود یک مدیر کل برای دامنه مورد نظر، نیاز دارد. هنگامی که در دیالوگ Configure Authentication روی OK کلیک میکنید، اطلاعات ورود یک مدیر کل از شما درخواست میشود و نهایتا هنگامیکه روی Create Project کلیک میکنید، ویژوال استودیو اپلیکیشن شما را در WAAD پیکربندی میکند.
برای اطلاعات بیشتر درباره نحوه استفاده از مدل احراز هویت Cloud - Single Organization به لینکهای زیر مراجعه فرمایید:
- Windows Azure Authentication
- Adding Sign-On to Your Web Application Using Windows Azure AD
- Developing ASP.NET Apps with Windows Azure Active Directory
از این گزینه برای احراز هویت کاربرانی استفاده کنید که در WAAD tenantهای متعددی تعریف شدهاند. برای مثال، نام سایت contoso.com است و برای کارمندان دو سازمان از طریق آدرسهای contoso.onmicrosoft.com و fabrikam.onmicrosoft.com قابل دسترسی خواهد بود. نحوه پیکربندی این مدل نیز مانند قسمت قبلی است.
برای اطلاعات بیشتر درباره احراز هویت Cloud - Multi Organization به لینکهای زیر مراجعه کنید:
- Easy Web App Integration with Windows Azure Active Directory, ASP.NET & Visual Studio
- Developing Multi-Tenant Web Applications with Windows Azure AD
این گزینه را هنگامی انتخاب کنید که کاربران در (Windows Server Active Directory (AD تعریف شده اند و نمیخواهید از WAAD استفاده کنید. از این مدل برای ایجاد وب سایتهای اینترنت و اینترانت میتوانید استفاده کنید. برای یک وب سایت اینترنتی از (Active Directory Federation Services (ADFS استفاده کنید.
برای یک وب سایت اینترانتی، میتوانید کلا این گزینه را رها کنید و از Windows Authentication استفاده کنید. در صورت استفاده از گزینه Windows Authentication لازم نیست تا آدرس سند متادیتا (metadata document URL) را فراهم کنید، همچنین توجه داشته باشید که Windows Authentication امکان کوئری گرفتن از پوشهها و کنترل سطوح دسترسی در Active Directory را ندارد.
On-Premises Authority
آدرس سند متادیتا. این سند اطلاعاتی درباره مختصات Authority دارد که اپلیکیشن از آنها برای به پیش بردن روند احراز هویت و ورود به سایت استفاده میکند.
Application ID URI
یک شناسه منحصر به فرد که AD از آن برای شناسایی اپلیکیشن استفاده میکند. میتوانید این فیلد را خالی رها کنید تا ویژوال استودیو بصورت خودکار اپلیکیشنی بدین منظور بسازد.
قدمهای بعدی
در این مقاله با مدلهای مختلف احراز هویت در اپلیکیشنهای Visual Studio 2013 آشنا شدید و برخی تغییرات و امکانات جدید نیز بررسی شدند. برای اطلاعات تکمیلی به ASP.NET and Web Tools for Visual Studio 2013 Release Notes مراجعه کنید.
ml5.js is a high level JavaScript library for machine learning. The main idea of this project is to further reduce the barriers between lower level machine learning and creative outputs using JavaScript