Why Serilog? It is easy to set up, has a clean API, and is portable between recent .NET platforms. The big difference between Serilog and the other frameworks is that it is designed to do structured logging out of the box. Another thing I really like about Serilog is that it can be configured via the appsetting.json
file alongside configuring through code. Changing logging configuration without touching the codebase is really helpful, especially in the production environment.
معرفی نگارش بعدی ASP.NET
ASP.NET MVC and Web API have been unified into a single programming model
No-compile developer experience
Dependency injection out of the box
Side by side - deploy the runtime and framework with your application
NuGet everything - even the runtime itself
All Open Source via the .NET Foundation and takes contributions
همچنین پشتیبانی رسمی از Mono توسط مایکروسافت:
انجام SEO بر روی برنامه های Angular
<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0"> <edmx:DataServices> <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="OwinAspNetCore.Models"> <EntityType Name="Product"> <Key> <PropertyRef Name="Id"/> </Key> <Property Name="Id" Type="Edm.Int32" Nullable="false"/> <Property Name="Name" Type="Edm.String"/> <Property Name="Price" Type="Edm.Decimal" Nullable="false"/> </EntityType> </Schema> <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="Default"> <Function Name="TestFunction" IsBound="true"> <Parameter Name="bindingParameter" Type="Collection(OwinAspNetCore.Models.Product)"/> <Parameter Name="Val" Type="Edm.Int32" Nullable="false"/> <Parameter Name="Name" Type="Edm.String"/> <ReturnType Type="Edm.Int32" Nullable="false"/> </Function> <EntityContainer Name="Container"> <EntitySet Name="Products" EntityType="OwinAspNetCore.Models.Product"/> </EntityContainer> </Schema> </edmx:DataServices> </edmx:Edmx>
string postUrl = "http://localhost:port/...."; HttpClient client = new HttpClient(); var response = client.PostAsync(postUrl, new StringContent(JsonConvert.SerializeObject(new { Rating = 5 }), Encoding.UTF8, "application/json")).Result;
آن را نصب نموده و بعد از تکمیل شدن، visual studio را restart کنید.
پروژهی console خود را باز کرده و از طریق Add -> new item، آیتم OData client را جستجو کرده و با نام ProductClient.tt آن را تولید نمایید (نام آن اختیاری است):
فایل ProductClient.tt را که یک T4 code generator میباشد، باز کرده و مقدار ثابت MetadataDocumentUri را به آدرس سرویس odata خود تغییر دهید:
public const string MetadataDocumentUri = "http://localhost:port/odata/";
روی این آیتم کلیک راست و گزینهی Run Custom tool را انتخاب نمایید. این تمام کاری است که نیاز به انجام دادن دارید.
حال فایل Program.cs را باز کرده و آنرا اینگونه تغییر دهید:
using ConsoleApplication1.OwinAspNetCore.Models; using System; using System.Linq; namespace ConsoleApplication1 { public class Program { static void Main(string[] args) { Uri uri = new Uri("http://localhost:24977/odata"); //var context = new Default.Container(uri); var context = new TestNameSpace.TestNameSpace(uri); //get var products = context.Products.Where(pr => pr.Name.Contains("a")) .Take(1).Select(pr => new { Firstname = pr.Name, PriceValue = pr.Price }).ToList(); //add context.AddToProducts(new Product() { Name = "Name1", Price = 123 }); //update Product p = context.Products.First(); p.Name = "changed"; context.UpdateObject(p); //delete context.DeleteObject(context.Products.Last()); //commit context.SaveChanges(); } } }
مشاهده میفرمایید که همهی عملیاتهای لازم برای CRUD، به شرط اینکه در سمت سرور طراحی شده باشند، به راحتی از سمت کلاینت قابل فراخوانی خواهند بود.
از این ویژگی فوق العاده میتوان حتی در کلاینتها جاوااسکریپتی نیز استفاده کرد. فرض کنید نرم افزار تحت وبی را با استفاده از jquery یا angularjs طراحی کردهاید. قاعدتا فراخوانی درخواستهای شما به سمت سرور، چیزی شبیه به این خواهد بود:
//angularjs $http.get("/products/get", {Name: "Test", Company: "Test"}) .then(function(response) { console.log(response.data); }); //jquery $.get("/products/get", {Name: "Test", Company: "Test"}, function(data, status){ console.log("Data: " + data); });
با استفاده از odata و typescript و یک library مربوط به odata client در سمت کلاینت، نرم افزار شما بجای موارد، بالا چیزی شبیه به مثال زیر خواهد بود (با همراه داشتن strongly typed و intellisense کامل)
let product1 = await context.products.filter(c => c.Name.contains("Ali")).toArray(); let product2 = await context.products.getSomeFunction(1, 'Test'); context.product.add({Name: 'Test'} as Product); await context.saveChanges()
در مقالههای آتی به ویژگیهای بیشتری از Odata خواهیم پرداخت.
کتابخانه animated_alert
خلاصهای از روشهای کار با کوکیها در ASP.NET Core
ایجاد یک کوکی جدید
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Core1RtmEmptyTest.Controllers { public class TestCookiesController : Controller { public IActionResult Index() { this.Response.Cookies.Append("key", "value", new CookieOptions { HttpOnly = true, Path = this.Request.PathBase.HasValue ? this.Request.PathBase.ToString() : "/", Secure = this.Request.IsHttps }); return Content("OK!"); } } }
همانطور که در تصویر نیز مشخص است، طول عمر این کوکی، به سشن تنظیم شدهاست و با پایان سشن جاری مرورگر (بسته شدن کل مرورگر)، این کوکی نیز غیرمعتبر شده و به صورت خودکار حذف خواهد شد. برای تعیین عمر دقیق یک کوکی میتوان از خاصیت Expires شیء CookieOptions که در مثال فوق مقدار دهی نشدهاست، استفاده کرد؛ مانند:
Expires = DateTimeOffset.UtcNow.AddDays(2)
خواندن محتویات کوکی ذخیره شده
پس از ثبت کوکی در Response، خواندن آن در Request بعدی به شکل زیر است:
var value = this.Request.Cookies["key"];
شیء this.Request.Cookies از نوع IRequestCookieCollection است:
public interface IRequestCookieCollection : IEnumerable<KeyValuePair<string, string>>, IEnumerable { string this[string key] { get; } ICollection<string> Keys { get; } bool ContainsKey(string key); bool TryGetValue(string key, out string value); }
در مستندات آن عنوان شدهاست که در حالت استفادهی از indexer، درصورت یافت نشدن کلید، string.Empty بازگشت داده میشود (که آزمایشات null را نمایش میدهند). اما در حالت استفادهی از TryGetValue بر اساس خروجی bool آن دقیقا میتوان مشخص کرد که آیا این کوکی وجود داشتهاست یا خیر.
در اینجا همچنین متد ContainsKey نیز جهت بررسی وجود یک کلید، در مجموعهی کلیدها نیز پیش بینی شدهاست.
بنابراین بهتر است جهت یافتن مقادیر کوکیها از روش ذیل استفاده کرد:
string cookieValue; if (this.Request.Cookies.TryGetValue(key, out cookieValue)) { // TODO: use the cookieValue } else { // this cookie doesn't exist. }
حذف کوکیهای موجود
در اینجا متد Delete نیز پیش بینی شدهاست که باید بر روی کوکیهای Response فراخوانی شود:
this.Response.Cookies.Delete("key");
همانطور که در تصویر نیز مشخص است، در صورت عدم تنظیم CookieOptions، این کوکی جدید اضافه شده، دارای تاریخ انقضای 1970 است که سبب خواهد شد تا توسط مرورگر، غیرمعتبر درنظر گرفته شده و حذف شود.
طراحی یک تامین کنندهی کوکیهای امن
پس از آشنایی با مقدمات کوکیها و همچنین «بررسی تغییرات رمزنگاری اطلاعات در NET Core.»، اکنون میتوان یک تامین کنندهی کوکیهای رمزنگاری شده را برای ASP.NET Core به نحو ذیل طراحی کرد:
public interface ISecureCookiesProvider { void Add(HttpContext context, string token, string value); bool Contains(HttpContext context, string token); string GetValue(HttpContext context, string token); void Remove(HttpContext context, string token); } public class SecureCookiesProvider : ISecureCookiesProvider { private readonly IProtectionProvider _protectionProvider; public SecureCookiesProvider(IProtectionProvider protectionProvider) { _protectionProvider = protectionProvider; } public void Add(HttpContext context, string token, string value) { value = _protectionProvider.Encrypt(value); context.Response.Cookies.Append(token, value, getCookieOptions(context)); } public bool Contains(HttpContext context, string token) { return context.Request.Cookies.ContainsKey(token); } public string GetValue(HttpContext context, string token) { string cookieValue; if (!context.Request.Cookies.TryGetValue(token, out cookieValue)) { return null; } return _protectionProvider.Decrypt(cookieValue); } public void Remove(HttpContext context, string token) { if (context.Request.Cookies.ContainsKey(token)) { context.Response.Cookies.Delete(token); } } /// <summary> /// Expires at the end of the browser's session. /// </summary> private CookieOptions getCookieOptions(HttpContext context) { return new CookieOptions { HttpOnly = true, Path = context.Request.PathBase.HasValue ? context.Request.PathBase.ToString() : "/", Secure = context.Request.IsHttps }; } }
- در این تامین کنندهی کوکیهای امن، IProtectionProvider تزریقی به سازندهی کلاس را در مطلب «تغییرات رمزنگاری اطلاعات در NET Core.» پیشتر ملاحظه کردهاید.
- در اینجا برای ثبت سرویس جدید، تنظیمات ابتدایی برنامه چنین شکلی را پیدا میکنند و پس از آن میتوان سرویس ISecureCookiesProvider را به کنترلرهای برنامه تزریق و استفاده کرد:
public class Startup { public void ConfigureServices(IServiceCollection services) { services.TryAddSingleton<IProtectionProvider, ProtectionProvider>(); services.TryAddSingleton<ISecureCookiesProvider, SecureCookiesProvider>();
بستهی Microsoft.AspNetCore.All فقط مخصوص پروژههای netcoreapp2.0 است
این «متا پکیج» تنها در پروژههایی که TargetFramework آنها به netcoreapp2.0 تنظیم شدهاست، قابل استفاده میباشد:
<TargetFramework>netcoreapp2.0</TargetFramework>
نحوهی به روز رسانی پروژهها جهت استفادهی از Microsoft.AspNetCore.All
پیش از ناقص کردن برنامه و حذف بستههای نیوگتی که نباید از فایل csproj حذف شوند، ابتدا باید لیستی را که توسط «متا پکیج» Microsoft.AspNetCore.All ارائه میشود، بررسی کرد. این لیست را پس از نصب SDK جدید، در آدرس ذیل میتوانید مشاهده کنید:
C:\Program Files\dotnet\store\x64\netcoreapp2.0
روش دیگر یافتن این لیست، مراجعهی به سایت نیوگت و بررسی قسمت dependencies آدرس https://www.nuget.org/packages/Microsoft.AspNetCore.All است:
سپس به فایل csproj ایی که دارای TargetFramework مساوی netcoreapp2.0 است مراجعه کرده و هر کدام از بستههایی را که در این لیست قرار دارند ... حذف کنید. در آخر بجای تمام این مداخل حذف شده، یک مدخل کلی ذیل را تعریف کنید:
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> </ItemGroup>
سؤال: آیا استفادهی از بستهی Microsoft.AspNetCore.All، ارائهی نهایی برنامه را حجیم نمیکند؟
اگر به مسیر dotnet\store ایی که پیشتر عنوان شد مراجعه کنید، در اینجا بیش از 180 بسته را خواهید یافت. در این حالت شاید به نظر برسد که حجم نهایی قابل توزیع برنامههای ASP.NET Core با استفاده از تک مدخل Microsoft.AspNetCore.All بسیار بالا خواهد رفت. اما ... خیر.
NET Core 2.0. به همراه ویژگی جدیدی است به نام Runtime store. هدف از آن، پیش نصب بستهها بر روی سیستم جاری، در یک مکان مرکزی است تا دیگر در حین توزیع نهایی برنامه، نیازی به توزیع مجدد آنها نباشد. به همین جهت، به آن میتوان شبیه به مفهوم پیشین Global assembly cache یا GAC مخصوص NET Core. نگاه کرد. به علاوه تمام این بستهها ngen شده و سرعت آغاز و اجرای برنامهها را بهبود میبخشند.
زمانیکه SDK جدید NET Core 2.0. را نصب میکنید، تمام بستههای مورد نیاز آن، در مسیر مرکزی C:\Program Files\dotnet\store نصب میشوند. بنابراین سیستمی که به همراه این SDK باشد، حتما حاوی تمام وابستگیهای ذکر شدهی در متاپکیج Microsoft.AspNetCore.All نیز خواهد بود و در این حالت نیازی به توزیع مجدد آنها نیست.
پس از آن مهمترین تفاوتی را که مشاهده خواهید کرد، کاهش حجم نهایی برنامههای ASP.NET Core 2.0 نسبت به نگارشهای 1x است. برای آزمایش، یک برنامهی ASP.NET Core 1.x و سپس یک برنامهی سادهی ASP.NET Core 2.x را publish کنید.
این تصویر، پوشهی نهایی قابل توزیع یک برنامهی ASP.NET Core 1.x را پس از publish نمایش میدهد:
و این تصویر، پوشهی نهایی قابل توزیع یک برنامهی ASP.NET Core 2.x را پس از publish نمایش میدهد:
در این حالت پوشهی نهایی نگارش 1x شامل 94 آیتم و پوشهی نهایی نگارش 2x شامل 13 آیتم است. یعنی حجم نهایی را که باید ارائه داد، به شدت کاهش یافتهاست.
بالا رفتن کارآیی تازه واردان به دنیای ASP.NET Core با متاپکیج جدید Microsoft.AspNetCore.All
یکی از مشکلاتی که به همراه کار با ASP.NET Core 1.x وجود دارد، مشخص نبودن محل قرارگیری ویژگیهای جدید و بستههای مرتبط با آنها است. همچنین به ازای هر ویژگی جدید باید یک بستهی نیوگت جدید را نصب کرد و عموما یافتن اینها و یا دانستن وجود آنها، کار دشواری میباشد.
اما زمانیکه متابستهی Microsoft.AspNetCore.All به قسمت ارجاعات پروژه اضافه میشود، در آغاز کار برنامه، سیستم IntelliSense آنها را پردازش کرده و بلافاصله در اختیار برنامه نویس قرار میگیرند. این قابلیت حتی در VSCode نیز همانند Visual Studio کار میکند و توسعه دهندهها بلافاصله IntelliSense بسیار کاملی را از قابلیتهای موجود در اختیار خواهند داشت؛ به همراه ویژگیهای تکمیلی دیگری مانند افزودن و یا اصلاح سادهتر فضاهای نام مرتبط.
تغییرات API دات نت 5 از دیدگاه افزونههای HttpClient
در اینجا لیست کامل متدهای الحاقی اضافه شدهی به فضای نام جدید و استاندارد System.Net.Http.Json را مشاهده میکنید:
namespace System.Net.Http.Json { public static class HttpClientJsonExtensions { public static Task<object> GetFromJsonAsync(this HttpClient client, string requestUri, Type type, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken)); public static Task<object> GetFromJsonAsync(this HttpClient client, string requestUri, Type type, CancellationToken cancellationToken = default(CancellationToken)); public static Task<object> GetFromJsonAsync(this HttpClient client, Uri requestUri, Type type, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken)); public static Task<object> GetFromJsonAsync(this HttpClient client, Uri requestUri, Type type, CancellationToken cancellationToken = default(CancellationToken)); public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, string requestUri, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken)); public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, string requestUri, CancellationToken cancellationToken = default(CancellationToken)); public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, Uri requestUri, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken)); public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, Uri requestUri, CancellationToken cancellationToken = default(CancellationToken)); public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken)); public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, CancellationToken cancellationToken); public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken)); public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, CancellationToken cancellationToken); public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken)); public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, CancellationToken cancellationToken); public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken)); public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, CancellationToken cancellationToken); } public static class HttpContentJsonExtensions { public static Task<object> ReadFromJsonAsync(this HttpContent content, Type type, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken)); public static Task<T> ReadFromJsonAsync<T>(this HttpContent content, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken)); } public sealed class JsonContent : HttpContent { public Type ObjectType { get; } public object Value { get; } public static JsonContent Create(object inputValue, Type inputType, MediaTypeHeaderValue mediaType = null, JsonSerializerOptions options = null); public static JsonContent Create<T>(T inputValue, MediaTypeHeaderValue mediaType = null, JsonSerializerOptions options = null); protected override void SerializeToStream(Stream stream, TransportContext context, CancellationToken cancellationToken); protected override Task SerializeToStreamAsync(Stream stream, TransportContext context); protected override Task SerializeToStreamAsync(Stream stream, TransportContext context, CancellationToken cancellationToken); protected override bool TryComputeLength(out long length); } }
متدهای الحاقی جدید کلاس HttpClientJsonExtensions
این متدها به صورت خلاصه شامل سه متد زیر میشوند:
- GetFromJsonAsync : یک درخواست Get را به آدرسی خاص ارسال کرده و خروجی JSON دریافتی را به کمک امکانات توکار System.Text.Json، پردازش و deserialize میکند.
- PostAsJsonAsync : یک درخواست POST را به آدرسی خاص، ارسال میکند. شیء ارسالی به آن به صورت خودکار به JSON تبدیل شده و سپس به سمت سرور ارسال میگردد.
- PutAsJsonAsync : یک درخواست PUT را به آدرسی خاص، ارسال میکند. شیء ارسالی به آن به صورت خودکار به JSON تبدیل شده و سپس به سمت سرور ارسال میگردد.
در ذیل چند مثال را در مورد نحوهی کار با این متدهای الحاقی جدید فضای نام استاندارد System.Net.Http.Json، مشاهده میکنید:
var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("https://localhost:5000"); var profiles = await httpClient.GetFromJsonAsync<Profile[]>("api/users/profiles"); var profile = new Profile { FirstName = "User 1", LastName = "Name 1", Age = 25 }; using var response1 = await httpClient.PostAsJsonAsync("api/users/profiles", profile); response1.EnsureSuccessStatusCode(); var updatedProfile = new Profile { FirstName = "User 2", LastName = "Name 2", Age = 40 }; using var response2 = await httpClient.PutAsJsonAsync("api/users/profiles", profile); response2.EnsureSuccessStatusCode();
اگر میخواستیم یک چنین کارهایی را پیش از دات نت 5 انجام دهیم، میبایستی قسمت Serialize کردن و همچنین تنظیم content-type را دستی انجام میدادیم:
var profile = new Profile { FirstName = "User 1", LastName = "Name 1", Age = 25 }; var json = JsonSerializer.Serialize(profile); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); using var response4 = await httpClient.PostAsync("api/users/profiles", stringContent); response4.EnsureSuccessStatusCode();
متدهای الحاقی جدید کلاس HttpContentJsonExtensions
این کلاس، متد الحاقی جدید ReadFromJsonAsync را ارائه میدهد که کار آن، خواندن یک محتوای HTTP از نوع HttpContent و deserialize آن به صورت JSON است. یک مثال:
var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("https://localhost:5000"); var request = new HttpRequestMessage(HttpMethod.Get, "api/users/profiles"); using var response1 = await httpClient.SendAsync(request); if (response1.IsSuccessStatusCode) { var profiles = await response1.Content.ReadFromJsonAsync<Profile[]>(); }
انجام اینکار در نگارشهای پیشین دات نت، نیاز به فراخوانی دستی JsonSerializer.DeserializeAsync را دارد:
var request = new HttpRequestMessage(HttpMethod.Get, "api/users/profiles"); using var response2 = await httpClient.SendAsync(request); if (response2.IsSuccessStatusCode) { using var streamResult = await response2.Content.ReadAsStreamAsync(); var profiles = JsonSerializer.DeserializeAsync<Profile[]>(streamResult); }
روشهای زیادی برای کار با HttpClient وجود دارند. یک روش آن، ساخت دستی HttpRequestMessage و سپس ارسال آن توسط متد SendAsync است؛ بجای استفاده از متد PostAsJsonAsync که بررسی شد. در این حالت با استفاده از متد جدید JsonContent.Create، میتوان کار تبدیل یک شیء را به JSON و همچنین تنظیم content-type را به صورت خودکار انجام داد:
var httpClient = new HttpClient(); var uri = "https://localhost:5000"; httpClient.BaseAddress = new Uri(uri); var requestMessage = new HttpRequestMessage(HttpMethod.Post, "https://localhost:5000") { Content = JsonContent.Create(new Profile { FirstName = "User 1", LastName = "Name 1", Age = 25 }) }; using var reponse1 = await httpClient.SendAsync(requestMessage); reponse1.EnsureSuccessStatusCode();
IIS 10.0 Express منتشر شد
Internet Information Services (IIS) 10.0 Express is a free, simple and
self-contained version of IIS that is optimized for developers. IIS 10.0
Express makes it easy to use the most current version of IIS to develop
and test websites. IIS 10.0 Express has all the core capabilities of
IIS 10.0 and additional features to ease website development.
The benefits of using IIS 10.0 Express include:
- The same web server that runs on your production server is now available on your development computer.
- Most tasks can be done without the need for administrative privileges.
- IIS Express runs on Windows 7 Service Pack 1 and all later versions of Windows.
- Many users can work independently on the same computer.