Some months ago a feature landed in Xamarin.Forms that seemed to truly polarize the Xamarin.Forms community: support for styling applications using CSS. Some argued that it was an unnecessary introduction to "Web" technology to the native development experience, and others that it simply isn't the right solution to the problem. While I sympathize with the latter opinion and think there's plenty of room for some good debate on the right path forward, I count myself as part of a third camp: I think that CSS is a powerful (and frequently maligned) solution to the problem of styling native mobile applications.
Redis 6.0.0 GA منتشر شد
The next major release of the popular data structure server is here. Redis is at the heart of so many data systems nowadays that any major release is big news but 6.0 packs in a lot of new bits and pieces that make it more robust and capable of modern workloads, including:
- Access Control Lists (ACL) for limiting what certain clients can do.
- Diskless replication on replicas.
- Threaded I/O (but Redis itself remains primarily single threaded).
- SSL/TLS support.
- A new client-server protocol, RESP3.
سری کار با IdentityServer 5
- Creating an IdentityServer 5 Project
- Adding JWT Bearer Authentication to an ASP.NET Core 5 API
- Adding Policy-Based Authorisation to an ASP.NET Core 5 API
- Authenticating a .NET 5 Console Application using IdentityServer 5
- Adding API Resources to IdentityServer 5
- Containerising a PostgreSQL 13 Database
- Adding Entity Framework Core 5 to IdentityServer 5
- Adding an OAuth 2.0 Security Scheme to an ASP.NET Core 5 API
- Adding ASP.NET Core 5 Identity to IdentityServer 5
- Seeding an ASP.NET Core 5 Identity Database with Users
- Authenticating an ASP.NET Core 5 Web Application using IdentityServer 5
- Authenticating an Angular 11 Single-Page Application using IdentityServer 5
- Authenticating a Flutter Android Application using IdentityServer 5
- Containerising an IdentityServer 5 Project
- Containerising an IdentityServer 5 Project with API Resources and Clients
برای قسمت backend، از همان برنامهی تکمیل شدهی قسمت قبل استفاده میکنیم که به آن تولید مقدماتی JWTها نیز اضافه شدهاست. البته این سری، مستقل از قسمت سمت سرور آن تهیه خواهد شد و صرفا در حد دریافت توکن از سرور و یا ارسال مشخصات کاربر جهت لاگین، نیاز بیشتری به قسمت سمت سرور آن ندارد و تاکید آن بر روی مباحث سمت کلاینت React است. بنابراین اینکه چگونه این توکن را تولید میکنید، در اینجا اهمیتی ندارد و کلیات آن با تمام روشهای پیاده سازی سمت سرور سازگار است (و مختص به فناوری خاصی نیست). پیشنیاز درک کدهای سمت سرور قسمت JWT آن، مطالب زیر هستند:
- «معرفی JSON Web Token»
- «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity»
- «پیاده سازی JSON Web Token با ASP.NET Web API 2.x»
- « آزمایش Web APIs توسط Postman - قسمت ششم - اعتبارسنجی مبتنی بر JWT»
ثبت یک کاربر جدید
فرم ثبت نام کاربران را در قسمت 21 این سری، در فایل src\components\registerForm.jsx، ایجاد و تکمیل کردیم. البته این فرم هنوز به backend server متصل نیست. برای کار با آن هم نیاز است شیءای را با ساختار زیر که ذکر سه خاصیت اول آن اجباری است، به endpoint ای با آدرس https://localhost:5001/api/Users به صورت یک HTTP Post ارسال کنیم:
{ "name": "string", "email": "string", "password": "string", "isAdmin": true, "id": 0 }
اکنون نوبت به اتصال کامپوننت registerForm.jsx، به سرویس backend است. تا اینجا دو سرویس src\services\genreService.js و src\services\movieService.js را در قسمت قبل، به برنامه جهت کار کردن با endpointهای backend server، اضافه کردیم. شبیه به همین روش را برای کار با سرویس سمت سرور api/Users نیز در پیش میگیریم. بنابراین فایل جدید src\services\userService.js را با محتوای زیر، به برنامهی frontend اضافه میکنیم:
import http from "./httpService"; import { apiUrl } from "../config.json"; const apiEndpoint = apiUrl + "/users"; export function register(user) { return http.post(apiEndpoint, { email: user.username, password: user.password, name: user.name }); }
اطلاعات شیء user ای که در اینجا دریافت میشود، از خاصیت data کامپوننت RegisterForm تامین میگردد:
class RegisterForm extends Form { state = { data: { username: "", password: "", name: "" }, errors: {} };
پس از تعریف userService.js، به registerForm.jsx بازگشته و ابتدا امکانات آنرا import میکنیم:
import * as userService from "../services/userService";
import { register } userService from "../services/userService";
doSubmit = async () => { try { await userService.register(this.state.data); } catch (ex) { if (ex.response && ex.response.status === 400) { const errors = { ...this.state.errors }; // clone an object errors.username = ex.response.data; this.setState({ errors }); } } };
در این حالت برای ارسال اطلاعات یک کاربر، در بار اول، یک چنین خروجی را از سمت سرور میتوان شاهد بود که id جدیدی را به این رکورد نسبت دادهاست:
اگر مجددا همین رکورد را به سمت سرور ارسال کنیم، اینبار خطای زیر را دریافت خواهیم کرد:
که از نوع 400 یا همان BadRequest است:
بنابراین نیاز است بدنهی response را در یک چنین مواردی که خطایی از سمت سرور صادر میشود، دریافت کرده و با به روز رسانی خاصیت errors در state فرم (همان قسمت بدنهی catch کدهای فوق)، سبب درج و نمایش خودکار این خطا شویم:
پیشتر در قسمت بررسی «کار با فرمها» آموختیم که برای مدیریت خطاهای پیش بینی شدهی دریافتی از سمت سرور، نیاز است قطعه کدهای مرتبط با سرویس http را در بدنهی try/catchها محصور کنیم. برای مثال در اینجا اگر ایمیل شخصی تکراری وارد شود، سرویس یک return BadRequest("Can't create the requested record.") را بازگشت میدهد که در اینجا status code معادل BadRequest، همان 400 است. بنابراین انتظار داریم که خطای 400 را از سمت سرور، تحت شرایط خاصی دریافت کنیم. به همین دلیل است که در اینجا تنها مدیریت status code=400 را در بدنهی catch نوشته شده ملاحظه میکنید.
سپس برای نمایش آن، نیاز است خاصیت متناظری را که این خطا به آن مرتبط میشود، با پیام دریافت شدهی از سمت سرور، مقدار دهی کنیم که در اینجا میدانیم مرتبط با username است. به همین جهت سطر errors.username = ex.response.data، کار انتساب بدنهی response را به خاصیت جدید errors.username انجام میدهد. در این حالت اگر به کمک متد setState، کار به روز رسانی خاصیت errors موجود در state را انجام دهیم، رندر مجدد فرم، در صف انجام قرار گرفته و در رندر بعدی آن، پیام موجود در errors.username، نمایش داده میشود.
پیاده سازی ورود به سیستم
فرم ورود به سیستم را در قسمت 18 این سری، در فایل src\components\loginForm.jsx، ایجاد و تکمیل کردیم که این فرم نیز هنوز به backend server متصل نیست. برای کار با آن نیاز است شیءای را با ساختار زیر که ذکر هر دو خاصیت آن اجباری است، به endpoint ای با آدرس https://localhost:5001/api/Auth/Login به صورت یک HTTP Post ارسال کنیم:
{ "email": "string", "password": "string" }
var jwt = _tokenFactoryService.CreateAccessToken(user); return Ok(new { access_token = jwt });
در ادامه برای تعامل با منبع api/Auth/Login سمت سرور، ابتدا یک سرویس مختص آنرا در فایل جدید src\services\authService.js، با محتوای زیر ایجاد میکنیم:
import { apiUrl } from "../config.json"; import http from "./httpService"; const apiEndpoint = apiUrl + "/auth"; export function login(email, password) { return http.post(apiEndpoint + "/login", { email, password }); }
import * as auth from "../services/authService"; class LoginForm extends Form { state = { data: { username: "", password: "" }, errors: {} }; // ... doSubmit = async () => { try { const { data } = this.state; const { data: { access_token } } = await auth.login(data.username, data.password); console.log("JWT", access_token); localStorage.setItem("token", access_token); this.props.history.push("/"); } catch (ex) { if (ex.response && ex.response.status === 400) { const errors = { ...this.state.errors }; errors.username = ex.response.data; this.setState({ errors }); } } };
- ابتدا تمام خروجیهای ماژول authService را با نام شیء auth دریافت کردهایم.
- سپس در متد doSubmit، اطلاعات خاصیت data موجود در state را که معادل فیلدهای فرم لاگین هستند، به متد auth.login برای انجام لاگین سمت سرور، ارسال کردهایم. این متد چون یک Promise را باز میگرداند، باید await شود و پس از آن متد جاری نیز باید به صورت async معرفی گردد.
- همانطور که عنوان شد، خروجی نهایی متد auth.login، یک شیء JSON دارای خاصیت access_token است که در اینجا از خاصیت data خروجی نهایی دریافت شدهاست.
- سپس نیاز است برای استفادههای آتی، این token دریافتی از سرور را در جایی ذخیره کرد. یکی از مکانهای متداول اینکار، local storage مرورگرها است (اطلاعات بیشتر).
- در آخر کاربر را توسط شیء history سیستم مسیریابی برنامه، به صفحهی اصلی آن هدایت میکنیم.
- در اینجا قسمت catch نیز ذکر شدهاست تا خطاهای حاصل از return BadRequestهای دریافتی از سمت سرور را بتوان ذیل فیلد نام کاربری نمایش داد. روش کار آن، دقیقا همانند روشی است که برای فرم ثبت یک کاربر جدید استفاده کردیم.
اکنون اگر برنامه را ذخیره کرده و اجرا کنیم، توکن دریافتی، در کنسول توسعه دهندگان مرورگر لاگ شده و سپس کاربر به صفحهی اصلی برنامه هدایت میشود. همچنین این token ذخیره شده را میتوان در ذیل قسمت application->storage آن نیز مشاهده کرد:
لاگین خودکار کاربر، پس از ثبت نام در سایت
پس از ثبت نام یک کاربر در سایت، بدنهی response بازگشت داده شدهی از سمت سرور، همان شیء user است که اکنون Id او مشخص شدهاست. بنابراین اینبار جهت ارائهی token از سمت سرور، بجای response body، از یک هدر سفارشی در فایل Controllers\UsersController.cs استفاده میکنیم (کدهای کامل آن در انتهای بحث پیوست شدهاست):
var jwt = _tokenFactoryService.CreateAccessToken(user); this.Response.Headers.Add("x-auth-token", jwt);
در ادامه در کدهای سمت کلاینت src\components\registerForm.jsx، برای استخراج این هدر سفارشی، اگر شیء response دریافتی از سرور را لاگ کنیم:
const response = await userService.register(this.state.data); console.log(response);
برای اینکه در کدهای سمت کلاینت بتوان این هدر سفارشی را خواند، نیاز است هدر مخصوص access-control-expose-headers را نیز به response اضافه کرد:
var jwt = _tokenFactoryService.CreateAccessToken(data); this.Response.Headers.Add("x-auth-token", jwt); this.Response.Headers.Add("access-control-expose-headers", "x-auth-token");
اکنون میتوان این هدر سفارشی را در متد doSubmit کامپوننت RegisterForm، از طریق شیء response.headers خواند و در localStorage ذخیره کرد. سپس کاربر را توسط شیء history سیستم مسیریابی، به ریشهی سایت هدایت نمود:
class RegisterForm extends Form { // ... doSubmit = async () => { try { const response = await userService.register(this.state.data); console.log(response); localStorage.setItem("token", response.headers["x-auth-token"]); this.props.history.push("/"); } catch (ex) { if (ex.response && ex.response.status === 400) { const errors = { ...this.state.errors }; // clone an object errors.username = ex.response.data; this.setState({ errors }); } } };
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-26-backend.zip و sample-26-frontend.zip
{"jti":"26bdfd20-104f-45d4-a4e1-111044808041", "iss":"http://localhost:5000/", "iat":1531729854, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier":"1", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"Vahid", "DisplayName":"وحید", "http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber":"046fb152a7474043952475cfa952cdc9", "http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata":"1", "DynamicPermission":[":MyProtectedApi2:Get", ":MyProtectedEditorsApi:Get", ":MyProtectedApi3:Get", ":MyProtectedApi4:Get"], "http://schemas.microsoft.com/ws/2008/06/identity/claims/role":["Admin", "Editor", "User"], "nbf":1531729855, "exp":1531729975, "aud":"Any"}
public bool CanUserAccess(ClaimsPrincipal user, string area, string controller, string action) { var currentClaimValue = $"{area}:{controller}:{action}"; var securedControllerActions = _mvcActionsDiscoveryService.GetAllSecuredControllerActionsWithPolicy(ConstantPolicies.DynamicPermission); if (!securedControllerActions.SelectMany(x => x.MvcActions).Any(x => x.ActionId == currentClaimValue)) { throw new KeyNotFoundException($@"The `secured` area={area}/controller={controller}/action={action} with `ConstantPolicies.DynamicPermission` policy not found. Please check you have entered the area/controller/action names correctly and also it's decorated with the correct security policy."); } if (!user.Identity.IsAuthenticated) { return false; } if (user.IsInRole("Admin")) { // Admin users have access to all of the pages. return true; } // Check for dynamic permissions // A user gets its permissions claims from the `ApplicationClaimsPrincipalFactory` class automatically and it includes the role claims too. //for check user has claim for access to action return user.HasClaim(claim => claim.Type == ConstantPolicies.DynamicPermissionClaimType && claim.Value == currentClaimValue); }
This package helps set up SqlClient in applications using dependency injection, notably ASP.NET and Worker Service applications. It allows easy configuration of your database connections and registers the appropriate services in your DI container. It also enables you to log events from Microsoft.Data.SqlClient using standard .NET logging (ILogger).
Visual Studio 2017 15.7 منتشر شد
- We added support to change installation locations.
- You can Save All your pending changes before you start your update.
- The update dialog provides you even more details about your update during installation.
- C# 7.3 is included in Visual Studio version 15.7.
- We improved solution load time for C# and VB projects.
- We made numerous updates to F# and its tools, with a focus on performance.
- We reduced the time to enable IntelliSense for large .NET Core projects by 25%.
- We made Quick Info improvements and new .NET refactorings like convert
for
-to-foreach
and make private fieldsreadonly
. - We added the ability to publish ASP.NET Core applications to App Service Linux without containers.
- Live Unit Testing works with embedded pdbs and supports projects that use reference assemblies.
- The Test Explorer has more responsive icons during test runs.
- C++ developers can use CodeLens for unit testing.
- We added new rules enforcing items from the C++ Core Guidelines.
- Debugging large solutions with /Debug:fastlink PDBs is more robust.
- CMake integration supports CMake 3.11 and static analysis.
- Python projects support type hints in IntelliSense, and a Run MyPy command has been added to look for typing errors in your code.
- Conda environments are supported in Python projects.
- We added a next version of our Python debugger based on the popular open source pydevd debugger.
- TypeScript 2.8 is included in Visual Studio version 15.7.
- We improved Kestrel HTTPs support during debugging.
- We added support for JavaScript debugging with Microsoft Edge.
- The Debugger supports VSTS and GitHub Authentication for Source Link.
- IntelliTrace’s step-back debugging feature is supported for debugging .NET Core projects.
- We added IntelliTrace support for taking snapshots on exceptions.
- We removed the blocking modal dialog from branch checkouts in Git when a solution or project reload is not required.
- There is an option to choose between OpenSSL and SChannel in Git.
- You can create and associate Azure Key Vaults from within the Visual Studio IDE.
- Visual Studio Tools for Xamarin can automatically install missing Android API levels required by Xamarin.Android projects.
- The Xamarin.Forms XAML editor provides IntelliSense and quick fixes for conditional XAML.
- We added support for Azure, UWP, and additional project types in Visual Studio Build Tools.
- You can create build servers without installing all of Visual Studio.
- The Windows 10 April 2018 Update SDK - Build 17134 is the default required SDK for the Universal Windows Platform development workload.
- We added support for Visual State Management for all UWP apps and more.
- We enabled automatic updates for sideloaded APPX packages.
- You have new tools for migrating to NuGet PackageReference.
- We added support for NuGet package signatures.
- We added Service Fabric Tooling for the 6.2 Service Fabric release.
- We updated Entity Framework Tools to work with the EF 6.2 runtime and to improve reverse engineering of existing databases.
امکان استفاده از گروپپالیسیها و...
Ubuntu 21.04 desktops can now join an AD domain at installation for central configuration. In turn, AD administrators can now manage Ubuntu workstations, which simplifies compliance with company policies.
The new Ubuntu also adds the ability to configure system settings from an AD domain controller. Using Ubuntu's AD Group Policy Client or other Group Policy Clients, system administrators can specify security policies on all connected clients, such as password policies and user access control, and desktop environment settings, such as login screen, background, and favorite programs.