<?xml version="1.0" encoding="utf-8"?> <Log xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Type>Verify</Type> <Gateway>Mellat</Gateway> <OrderNumber>636696338698017740</OrderNumber> <Amount>50000</Amount> <ReferenceId>63610A4322A08FDD</ReferenceId> <TransactionId>139032371054</TransactionId> <Status>Success</Status> <Message>تراکنش با موفقیت انجام شد</Message> <CreatedOn>2018-08-12T01:25:31.1869631+04:30</CreatedOn> </Log>
ASP.NET MVC و Identity 2.0 : مفاهیم پایه
- فیلد تاریخ انقضاء یک نقش بهتر است به جدول Role اضافه شود و نه این جدول واسط.
ممنون، ولی فکر کنم منظورم رو درست نرسوندم، قرار نیست تاریخ انقضا به خود نقش تعلق بگیره، به فرض یک نقش VIP در جدول Roles ایجاد شده و تا ابد هم وجود داره، پس تاریخ انقضایی هم برای این نقش نباید در نظر گرفت. ولی کاربری که پرداخت رو انجام بده به مدت فرضا سه ماه نقش VIP بهش تعلق میگیره. حالا بعد از گذشت سه ماه دیگه این کاربر حق عملیاتی که کاربران VIP مجاز به انجام اون هستند رو نخواهد داشت.
شروع به کار با Ember.js
- مقایسهای در اینجا «AngularJS vs Ember»
- مقایسهای کاملتر در اینجا «AngularJs vs EmberJs»
- «Rails JS frameworks: Ember.js vs. AngularJS»
- «AngularJS vs. Backbone.js vs. Ember.js»
- یک نمونهی دیگر «The Top 10 Javascript MVC Frameworks Reviewed»
- این نظرسنجی را هم دنبال کنید: «آیا به یادگیری یا ادامهی استفاده از AngularJS خواهید پرداخت؟»
- همچنین از jquery-1.8.3.min.js برای کار با این نسخه استفاده میکنم.
- اگر نیاز به نگارش دیگری دارید بهتر است در انجمن تهیه کنندگان آن این مسایل رو مطرح کنید. البته ابتدا باید هزینه لایسنس نگارشهای جدید آنرا پرداخت کنید.
- بسیاری از افزونههای jQuery، با نگارشهای جدید بعد از 1.9 آن سازگار نیستند و فقط این یک مورد نیست. بهتر است عجله نکنید و حداقل 6 ماهی برای ارتقاء صبر کنید.
- پروژهای وجود دارد به نام jQuery Migrate برای پوشش مواردی که از جیکوئری 1.9 به بعد حذف شدن. این مورد رو باید به پروژه اضافه کنید تا با افزونههای قدیمی بتونید کار کنید.
بررسی تصویر امنیتی (Captcha) سایت - قسمت دوم
در واقع کپچا یک تصویر تزیینی میشود.
دستکاری اطلاعات ارسالی به سرور، فقط در اینجا مطرح نیست. عمده مورد استفاده آن در ویرایش idها (مثلا در DropDownList ها) ست. (ثبت نام مسکن برای شهری که هنوز ثبت نام آن فعال نشده، خرید بلیت جایگاه ویژه برای یک کنسرت، پرداخت بیمه با دستکاری هزینه و ...)
امتیازات تکمیلی حضور در محل کار شما
امن سازی برنامههای ASP.NET Core توسط IdentityServer 4x - قسمت نهم- مدیریت طول عمر توکنها
در نسخه آخر apiهای نظیر TokenClient - DiscoveryClient و ... منسوخ شده است و توصیه شده با HttpClient جهت استفاده جایگزین شود.
مستندات
متد RenewTokens مطلب جاری به صورت زیر بازنویسی میشود
private async Task<string> RenewTokens()
{
// get the current HttpContext to access the tokens
var currentContext = _httpContextAccessor.HttpContext;
var disco = await _httpClient.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
{
Address = _configuration["IdentityServer:IDPBaseAddress"]
});
if (disco.IsError) throw new Exception(disco.Error);
// get the saved refresh token
var currentRefreshToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
// refresh the tokens
var response = await _httpClient.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = _configuration["IdentityServer:ClientId"],
ClientSecret = _configuration["IdentityServer:ClientSecret"],
RefreshToken = currentRefreshToken
});
if (response.IsError)
{
throw new Exception("Problem encountered while refreshing tokens.", response.Exception);
}
// update the tokens & expiration value
var updatedTokens = new List<AuthenticationToken>();
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = response.IdentityToken
});
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = response.AccessToken
});
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = response.RefreshToken
});
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn);
updatedTokens.Add(new AuthenticationToken
{
Name = "expires_at",
Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
});
// get authenticate result, containing the current principal & properties
var currentAuthenticateResult = await currentContext.AuthenticateAsync("Cookies");
// store the updated tokens
currentAuthenticateResult.Properties.StoreTokens(updatedTokens);
// sign in
await currentContext.SignInAsync("Cookies",
currentAuthenticateResult.Principal, currentAuthenticateResult.Properties);
// return the new access token
return response.AccessToken;
}
معرفی Blazor Hybrid
- در Blazor Web Assembly که UI با HTML / CSS زده میشود، کدهای C# .NET ای با کمک Web Assembly و داخل خود مرورگر اجرا میشوند. با کمک Blazor Web Assembly میتوان محصولات PWA و SPA ایجاد نمود.
- در Blazor Server که UI با HTML / CSS زده میشود، کدها در سرور اجرا و به وسیلهی Web Sockets، تعاملات UI ای از Browser به سرور ارسال و تغییرات UI ای از سرور به Browser ارسال میشوند. با کمک Blazor Server میتوان محصولات SPA ایجاد نمود.
- Blazor Native Mobile Apps که در این روش از کامپوننتهای Native موبایل استفاده میشود؛ نه عناصر HTML مانند h1 و div. با کمک Blazor Native Mobile Apps میتوان برنامههای Native موبایل برای Android / iOS و برنامههای Desktop برای Windows ایجاد نمود.
- Blazor Hybrid که در این روش UI با HTML / CSS بوده، ولی اجرای کدهای C# .NET داخل خود سیستم عامل و به صورت Native است. با کمک Blazor Hybrid میتوان برنامههای موبایل برای Android / iOS و برنامههای Desktop برای Windows ایجاد نمود.
namespace OpenAPISwaggerDoc.Web.Controllers { [Route("api/[controller]")] [ApiController] public class ConventionTestsController : ControllerBase { // GET: api/ConventionTests/5 [HttpGet("{id}", Name = "Get")] [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Get))] public string Get(int id) { return "value"; }
using Microsoft.AspNetCore.Mvc.ApiExplorer; namespace Microsoft.AspNetCore.Mvc { public static class DefaultApiConventions { [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] [ProducesDefaultResponseType] [ProducesResponseType(200)] [ProducesResponseType(404)] public static void Get( [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] object id); } }
امکان اعمال DefaultApiConventions به تمام متدهای یک کنترلر API نیز به صورت زیر با استفاده از ویژگی ApiConventionType اعمال شدهی به کلاس کنترلر میسر است:
namespace OpenAPISwaggerDoc.Web.Controllers { [Route("api/[controller]")] [ApiController] [ApiConventionType(typeof(DefaultApiConventions))] public class ConventionTestsController : ControllerBase
[assembly: ApiConventionType(typeof(DefaultApiConventions))] namespace OpenAPISwaggerDoc.Web { public class Startup
ایجاد ApiConventions سفارشی
همانطور که عنوان شد، اگر متدهای API شما دقیقا همان نامهای پیشفرض Get/Post/Put/Delete را داشته باشند، توسط DefaultApiConventions مدیریت خواهند شد. در سایر حالات، مثلا اگر بجای نام Post، از نام Insert استفاده شد، باید ApiConventions سفارشی را ایجاد کرد:
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApiExplorer; namespace OpenAPISwaggerDoc.Web.AppConventions { public static class CustomConventions { [ProducesDefaultResponseType] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] public static void Insert( [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] object model) { } } }
پس از آن برای اعمال این ApiConventions جدید میتوان به صورت زیر عمل کرد:
namespace OpenAPISwaggerDoc.Web.Controllers { [Route("api/[controller]")] [ApiController] public class ConventionTestsController : ControllerBase { [HttpPost] [ApiConventionMethod(typeof(CustomConventions), nameof(CustomConventions.Insert))] public void Insert([FromBody] string value) { }
سؤال: آیا استفادهی از این ApiConventions ایدهی خوبی است؟
همانطور که در ابتدای بحث نیز عنوان شد، اگر فیلترهای سراسری را مانند قسمت قبل فعال کرده باشیم، از اعمال ApiConventions صرفنظر میشود. همچنین حالت پیشفرض آنها برای حالتهای متداول و ساده مفید هستند و برای سایر حالات باید کدهای زیادی را نوشت. به همین جهت خود مایکروسافت هم استفادهی از ApiConventions را صرفا برای کنترلرهای API ای که دقیقا مطابق با قالب پیشفرض آنها تهیه شدهاند، توصیه میکند. بنابراین استفادهی از Attributes که در قسمت قبل آنها را بررسی کردیم، مقدم هستند بر استفادهی از ApiConventions و تعدادی از بهترین تجربههای کاربری در این زمینه به شرح زیر میباشند:
- از API Analyzers که در قسمت قبل معرفی شد، برای یافتن کمبودهای نقایص مستندات استفاده کنید.
- از ویژگی ProducesDefaultResponseType استفاده کنید؛ اما تا جائیکه میتوانید، جزئیات ممکن را به صورت صریحی مستند نمائید.
- Attributes را به صورت سراسری معرفی کنید.
بهبود مستندات Content negotiation
فرض کنید میخواهید لیست کتابهای یک نویسنده را دریافت کنید. در اینجا خروجی ارائه شده، با فرمت JSON تولید میشود؛ اما ممکن است XML ای نیز باشد و یا حالتهای دیگر، بسته به تنظیمات برنامه. کار Content negotiation این است که مصرف کنندهی یک API، دقیقا مشخص کند، چه نوع فرمت خروجی را مدنظر دارد. هدری که برای این منظور استفاده میشود، accept header نامدارد و ذکر آن اجباری است؛ هر چند تعدادی از APIها بدون وجود آن نیز سعی میکنند حالت پیشفرضی را ارائه دهند.
Swagger-UI به نحوی که در تصویر فوق ملاحظه میکنید، امکان انتخاب Accept header را مسیر میکند. در این حالت اگر application/json را انتخاب کنیم، خروجی JSON ای را دریافت میکنیم. اما اگر text/plain را انتخاب کنیم، چون توسط API ما پشتیبانی نمیشود، خروجی از نوع 406 یا همان Status406NotAcceptable را دریافت خواهیم کرد. بنابراین وجود گزینهی text/plain در اینجا غیرضروری و گمراه کنندهاست و نیاز است این مشکل را برطرف کرد:
namespace OpenAPISwaggerDoc.Web.Controllers { [Produces("application/json")] [Route("api/authors/{authorId}/books")] [ApiController] public class BooksController : ControllerBase
پس از اعمال این ویژگی، تاثیر آنرا بر روی Swagger-UI در شکل زیر مشاهده میکنید که اینبار تنها به یک مورد مشخص، محدود شدهاست:
در اینجا اگر قصد داشته باشیم خروجی XML را نیز پشتیبانی کنیم، میتوان به صورت زیر عمل کرد:
- ابتدا در کلاس Startup، نیاز است OutputFormatter متناظری را به سیستم معرفی نمود:
namespace OpenAPISwaggerDoc.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(setupAction => { setupAction.OutputFormatters.Add(new XmlSerializerOutputFormatter());
namespace OpenAPISwaggerDoc.Web.Controllers { [Produces("application/json", "application/xml")] [Route("api/authors/{authorId}/books")] [ApiController] public class BooksController : ControllerBase
در این حالت اگر Controls Accept header را در UI از نوع xml انتخاب کنیم و سپس با کلیک بر روی دکمهی try it out و ذکر id یک نویسنده، لیست کتابهای او را درخواست کنیم، خروجی نهایی XML ای آن قابل مشاهده خواهد بود:
البته تا اینجا فقط Swagger-UI را جهت محدود کردن به دو نوع خروجی با فرمت JSON و XML، اصلاح کردهایم؛ اما این مورد به معنای محدود کردن سایر ابزارهای آزمایش یک API مانند postman نیست. در این نوع موارد، تمام مدیاتایپهای ارسالی پشتیبانی نشده، سبب تولید خروجی با فرمت JSON میشوند. برای محدود کردن آنها به خروجی از نوع 406 میتوان تنظیم ReturnHttpNotAcceptable را به true انجام داد تا اگر برای مثال درخواست application/xyz ارسال شد، صرفا یک استثناء بازگشت داده شود و نه خروجی JSON:
namespace OpenAPISwaggerDoc.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(setupAction => { setupAction.ReturnHttpNotAcceptable = true; // Status406NotAcceptable
بهبود مستندات نوع بدنهی درخواست
تا اینجا فرمت accept header را دقیقا مشخص و مستند کردیم؛ اما اگر به تصویر فوق دقت کنید، در حین ارسال اطلاعاتی از نوع POST به سرور، چندین نوع Request body را میتوان انتخاب کرد که الزاما تمام آنها توسط API ما پشتیبانی نمیشود. برای رفع این مشکل میتوان از ویژگی Consumes استفاده کرد که نوع مدیتاتایپهای مجاز ورودی را مشخص میکند:
namespace OpenAPISwaggerDoc.Web.Controllers { [Produces("application/json", "application/xml")] [Route("api/authors/{authorId}/books")] [ApiController] public class BooksController : ControllerBase { /// <summary> /// Create a book for a specific author /// </summary> /// <param name="authorId">The id of the book author</param> /// <param name="bookForCreation">The book to create</param> /// <returns>An ActionResult of type Book</returns> [HttpPost()] [Consumes("application/json")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task<ActionResult<Book>> CreateBook( Guid authorId, [FromBody] BookForCreation bookForCreation) {
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: OpenAPISwaggerDoc-05.zip
همه کنترلها در Xamarin Forms دارای Property ای با نام FlowDirection هستند که مقادیر RightToLeft، LeftToRight و MatchParent را میپذیرد. MatchParent که مقدار پیش فرض است، به این معنی است که مثلا اگر در ContentPage، مقدار FlowDirection را RightToLeft دهیم، تمامی کنترلهای داخل آن صفحه RightToLeft باشند و بالعکس.
مجددا یک Resources file را با نام Strings.fa.resx و یکی دیگر را با نام Strings.en.resx اضافه کنید. برای درک بهتر وضعیت نهایی، پروژه XamApp را Clone/Pull کنید و آن را بررسی کنید.
در فایل Strings.resx یک ردیف جدید اضافه کنید که Name آن برابر با HelloWorld باشد و Value آن خالی است. این نام، در کد نویسی ما استفاده میشود و مثلا نباید شامل Space، علامت ! و ... باشد. در فایل Strings.fa.resx یک ردیف جدید اضافه کنید که Name آن برابر با همان HelloWorld باشد و Value آن برابر با سلام دنیا! در نهایت در فایل Strings.en.resx یک ردیف جدید را اضافه کنید که Name آن HelloWorld بوده و Value آن ! Hello world باشد.
سپس در فایل App.xaml.cs میتوانید قبل از اولین NavigationService.NavigateAsync، از کد زیر را استفاده کنید:
Strings.Culture = CultureInfo.CurrentUICulture = new CultureInfo("en"); // or new CultureInfo("fa");
برای نمایش پیام در View Model با استفاده از IUserDialogs نیز میتوانید به شکل زیر عمل کنید:
await UserDialogs.AlertAsync(message: Strings.HelloWorld);
در صورتیکه بخواهید پارامتری را در stringهای چند زبانه خود داشته باشید نیز میتوانید به شکل زیر عمل کنید:
Name | En Value | Fa Value |
ButtonTappedCount | Button tapped {0} times! | دکمه {0} کلیک شده است |
<Label Text="{Binding StepsCount, StringFormat={x:Static resx:Strings.ButtonTappedCount}}" />
namespace مربوطه یعنی resx هم در بالای فایل Xaml باید قرار داده شود، که میشود:
xmlns:resx="clr-namespace:XamApp.Resources"
await UserDialogs.AlertAsync(string.Format(Strings.ButtonTappedCount, StepsCount));