اگر مدلها در چندین پروژه بودن چطور؟
مثلا به صورت ماژول هر ماژول مدل خود را دارد
PM> Install-Package Microsoft.CodeAnalysis
using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Roslyn01 { class Program { static void Main(string[] args) { parseText(); } static void parseText() { var tree = CSharpSyntaxTree.ParseText("class Foo { void Bar(int x) {} }"); Console.WriteLine(tree.ToString()); Console.WriteLine(tree.GetRoot().NormalizeWhitespace().ToString()); var res = SyntaxFactory.ClassDeclaration("Foo") .WithMembers(SyntaxFactory.List<MemberDeclarationSyntax>(new[] { SyntaxFactory.MethodDeclaration( SyntaxFactory.PredefinedType( SyntaxFactory.Token(SyntaxKind.VoidKeyword) ), "Bar" ) .WithBody(SyntaxFactory.Block()) })) .NormalizeWhitespace(); Console.WriteLine(res); } } }
tree.GetRoot().NormalizeWhitespace().ToString()
class Foo { void Bar(int x) { } }
static void querySyntaxTree() { var tree = CSharpSyntaxTree.ParseText("class Foo { void Bar() {} }"); var node = (CompilationUnitSyntax)tree.GetRoot(); // Using the object model foreach (var member in node.Members) { if (member.Kind() == SyntaxKind.ClassDeclaration) { var @class = (ClassDeclarationSyntax)member; foreach (var member2 in @class.Members) { if (member2.Kind() == SyntaxKind.MethodDeclaration) { var method = (MethodDeclarationSyntax)member2; // do stuff } } } } // Using LINQ query methods var bars = from member in node.Members.OfType<ClassDeclarationSyntax>() from member2 in member.Members.OfType<MethodDeclarationSyntax>() where member2.Identifier.Text == "Bar" select member2; var res = bars.ToList(); // Using visitors new MyVisitor().Visit(node); }
class MyVisitor : CSharpSyntaxWalker { public override void VisitMethodDeclaration(MethodDeclarationSyntax node) { if (node.Identifier.Text == "Bar") { // do stuff } base.VisitMethodDeclaration(node); } }
new MyVisitor().Visit(node);
public IActionResult Get() { var user = this.User.Identity as ClaimsIdentity; var config = new { userName = user.Name, roles = user.Claims.Where(x => x.Type == ClaimTypes.Role).Select(x => x.Value).ToList() }; return Ok(config); }
{ "jti": "d1272eb5-1061-45bd-9209-3ccbc6ddcf0a", "iss": "http://localhost/", "iat": 1513070340, "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": "709b64868a1d4d108ee58369f5c3c1f3", "http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata": "1", "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": [ "Admin", "User" ], "nbf": 1513070340, "exp": 1513070460, "aud": "Any" }
export interface AuthUser { userId: string; userName: string; displayName: string; roles: string[]; }
getAuthUser(): AuthUser { if (!this.isLoggedIn()) { return null; } const decodedToken = this.getDecodedAccessToken(); let roles = decodedToken["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]; if (roles) { roles = roles.map(role => role.toLowerCase()); } return Object.freeze({ userId: decodedToken["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"], userName: decodedToken["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"], displayName: decodedToken["DisplayName"], roles: roles }); }
import { ProtectedPageComponent } from "./protected-page/protected-page.component"; import { AuthGuardPermission } from "../core/models/auth-guard-permission"; const routes: Routes = [ { path: "protectedPage", component: ProtectedPageComponent, data: { permission: { permittedRoles: ["Admin"], deniedRoles: null } as AuthGuardPermission } } ];
export interface AuthGuardPermission { permittedRoles?: string[]; deniedRoles?: string[]; }
>ng g c Authentication/AccessDenied
AccessDenied create src/app/Authentication/access-denied/access-denied.component.html (32 bytes) create src/app/Authentication/access-denied/access-denied.component.ts (296 bytes) create src/app/Authentication/access-denied/access-denied.component.css (0 bytes) update src/app/Authentication/authentication.module.ts (550 bytes)
import { LoginComponent } from "./login/login.component"; import { AccessDeniedComponent } from "./access-denied/access-denied.component"; const routes: Routes = [ { path: "login", component: LoginComponent }, { path: "accessDenied", component: AccessDeniedComponent } ];
<h1 class="text-danger"> <span class="glyphicon glyphicon-ban-circle"></span> Access Denied </h1> <p>Sorry! You don't have access to this page.</p> <button class="btn btn-default" (click)="goBack()"> <span class="glyphicon glyphicon-arrow-left"></span> Back </button> <button *ngIf="!isAuthenticated" class="btn btn-success" [routerLink]="['/login']" queryParamsHandling="merge"> Login </button>
import { Component, OnInit } from "@angular/core"; import { Location } from "@angular/common"; import { AuthService } from "../../core/services/auth.service"; @Component({ selector: "app-access-denied", templateUrl: "./access-denied.component.html", styleUrls: ["./access-denied.component.css"] }) export class AccessDeniedComponent implements OnInit { isAuthenticated = false; constructor( private location: Location, private authService: AuthService ) { } ngOnInit() { this.isAuthenticated = this.authService.isLoggedIn(); } goBack() { this.location.back(); // <-- go back to previous location on cancel } }
isAuthUserInRoles(requiredRoles: string[]): boolean { const user = this.getAuthUser(); if (!user || !user.roles) { return false; } return requiredRoles.some(requiredRole => user.roles.indexOf(requiredRole.toLowerCase()) >= 0); } isAuthUserInRole(requiredRole: string): boolean { return this.isAuthUserInRoles([requiredRole]); }
import { Injectable } from "@angular/core"; import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; import { AuthService } from "./auth.service"; import { AuthGuardPermission } from "../models/auth-guard-permission"; @Injectable() export class AuthGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { if (!this.authService.isLoggedIn()) { this.showAccessDenied(state); return false; } const permissionData = route.data["permission"] as AuthGuardPermission; if (!permissionData) { return true; } if (Array.isArray(permissionData.deniedRoles) && Array.isArray(permissionData.permittedRoles)) { throw new Error("Don't set both 'deniedRoles' and 'permittedRoles' in route data."); } if (Array.isArray(permissionData.permittedRoles)) { const isInRole = this.authService.isAuthUserInRoles(permissionData.permittedRoles); if (isInRole) { return true; } this.showAccessDenied(state); return false; } if (Array.isArray(permissionData.deniedRoles)) { const isInRole = this.authService.isAuthUserInRoles(permissionData.deniedRoles); if (!isInRole) { return true; } this.showAccessDenied(state); return false; } } private showAccessDenied(state: RouterStateSnapshot) { this.router.navigate(["/accessDenied"], { queryParams: { returnUrl: state.url } }); } }
this.returnUrl = this.route.snapshot.queryParams["returnUrl"];
if (this.returnUrl) { this.router.navigate([this.returnUrl]); } else { this.router.navigate(["/protectedPage"]); }
import { AuthGuard } from "./services/auth.guard"; @NgModule({ providers: [ AuthGuard ] }) export class CoreModule {}
import { ProtectedPageComponent } from "./protected-page/protected-page.component"; import { AuthGuardPermission } from "../core/models/auth-guard-permission"; import { AuthGuard } from "../core/services/auth.guard"; const routes: Routes = [ { path: "protectedPage", component: ProtectedPageComponent, data: { permission: { permittedRoles: ["Admin"], deniedRoles: null } as AuthGuardPermission }, canActivate: [AuthGuard] } ];
table#tblUsers.table.table-striped.table-bordered.table-hover>thead>tr>th{row}+th{Name}+th{Last Name}+th{Operations}^^tbody>tr*6>(td{row}+td{FirstName}+td{LastName}+td>button.btn.btn-primary{Edit}+button.btn.btn-danger{Delete})
<table id="tblUsers" class="table table-striped table-bordered table-hover"> <thead> <tr> <th>row</th> <th>Name</th> <th>Last Name</th> <th>Operations</th> </tr> </thead> <tbody> <tr> <td>row</td> <td>FirstName</td> <td>LastName</td> <td> <button>Edit</button> <button>Delete</button></td> </tr> <tr> <td>row</td> <td>FirstName</td> <td>LastName</td> <td> <button class="btn btn-primary">Edit</button> <button class="btn btn-danger">Delete</button></td> </tr> <tr> <td>row</td> <td>FirstName</td> <td>LastName</td> <td> <button class="btn btn-primary">Edit</button> <button class="btn btn-danger">Delete</button></td> </tr> <tr> <td>row</td> <td>FirstName</td> <td>LastName</td> <td> <button class="btn btn-primary">Edit</button> <button class="btn btn-danger">Delete</button></td> </tr> <tr> <td>row</td> <td>FirstName</td> <td>LastName</td> <td> <button class="btn btn-primary">Edit</button> <button class="btn btn-danger">Delete</button></td> </tr> <tr> <td>row</td> <td>FirstName</td> <td>LastName</td> <td> <button class="btn btn-primary">Edit</button> <button class="btn btn-danger">Delete</button></td> </tr> </tbody> </table>
ul>li[ng-repeat="user in Users"]>p[ng-bind="{{user.UserName}}"]+a{Details}
<ul> <li ng-repeat="user in Users"> <p ng-bind="{{user.UserName}}"></p> <a href="">Details</a> </li> </ul>
<!-- Type this --> div.container <!-- Creates this --> <div class="container"></div>
<!-- Type this --> ul#userList <!-- Creates this --> <ul id="userList"></ul>
<!-- Type this --> div.container#wrapper <!-- Creates this --> <div class="container" id="wrapper"></div>
<!-- Type this --> button.btn.btn-primary <!-- Creates this --> <button class="btn btn-primary"></button>
<!-- Type this --> .container <!-- Creates this --> <div class="container"></div>
<!-- Type this --> div.container>div#header <!-- Creates this --> <div class="container"> <div id="header"></div> </div>
<!-- Type this --> div.navbar>div.navbar-inner>ul.navbar <!-- Creates this --> <div class="navbar"> <div class="navbar-inner"> <ul class="navbar"></ul> </div> </div>
<!-- Type this --> div[title] <!-- Creates this --> <div title=""></div>
<!-- Type this --> input[type="text" placeholder="First Name"] <!-- Creates this --> <input type="text" value="" placeholder="First Name" />
<!-- Type this --> ul[ng-repeat="user in Users"]>li[ng-model="user.FirstName"] <!-- Creates this --> <ul ng-repeat="user in Users"> <li ng-model="user.FirstName"></li> </ul>
{} متن (Text)
این عملگر، متن مورد نظر شما را داخل عنصر قرار میدهد.
<!-- Type this --> div>h1{My Title} <!-- Creates this --> <div> <h1>My Title</h1> </div>
<!-- Type this --> form>input[type="text"]+input[type="checkbox"] <!-- Creates this --> <form> <input type="text" value="" /> <input type="checkbox" value="" /> </form>
<!-- Type this --> div#header>img+h1{Title} <!-- Creates this --> <div id="header"> <img src="" alt="" /> <h1>Title</h1> </div>
<!-- Type this --> table>thead>tr>th{row}^^tbody>tr>td{row1} <!-- Creates this --> <table> <thead> <tr> <th>row</th> </tr> </thead> <tbody> <tr> <td>row1</td> </tr> </tbody> </table>
<!-- Type this --> ul>li*3>p{Hello} <!-- Creates this --> <ul> <li> <p> Hello </p> </li> <li> <p> Hello </p> </li> <li> <p> Hello </p> </li> </ul>
<!-- Type this --> (div.container>h1{title}+div.content{Some Text Here})*2 <!-- Creates this --> <div class="container"> <h1>title</h1> <div class="content"> Some Text Here </div> </div> <div class="container"> <h1>title</h1> <div class="content"> Some Text Here </div> </div>
<!-- Type this --> ul>li*2>p{item $} <!-- Creates this --> <ul> <li> <p> item 1 </p> </li> <li> <p> item 2 </p> </li> </ul>
<!-- Type this --> ul>li*2>p{item $$$} <!-- Creates this --> <ul> <li> <p> item 001 </p> </li> <li> <p> item 002 </p> </li> </ul>
<!-- Type this --> div>(header>div)+section>(ul>li*2>a)+footer>(div>span) <!-- Creates this --> <div> <header> <div></div> </header> <section> <ul> <li><a href=""></a></li> <li><a href=""></a></li> </ul> <footer> <div> <span></span> </div> </footer> </section> </div>
<!-- Type this --> div>(h1>lorem5)+(h3>lorem3) <!-- Creates this --> <div> <h1>Lorem ipsum dolor sit amet.</h1> <h3>Lorem ipsum dolor.</h3> </div>
ابتدا از طریق فرمت protocol buffer، فایلهای خود را که قرار است انتقال داده شوند، مینویسیم.
سپس بصورت خودکار برای زبان برنامه نویسی مطبوع خود آن را generate میکنیم.
کدهای تولید شده بصورت خودکار و کاملا آماده هستند و ضمن اینکه encode/decode شدن بصورت خودکار توسط فریم ورک انجام شده و قابلیت تعامل بین زبانهای مختلف برنامه نویسی یا سرویسهای مختلف برقرار است.
نکته:
نصب Code generator
برای اینکه بتوانیم از طریق فایلهایی که میسازیم کدهای generate شده را تولید کنیم، احتیاج به کامپایلر مربوطه را داریم.
اگر از MacOSX استفاده میکنید، به راحتی با استفاده از دستور زیر میتوانید آن را نصب کنید:
brew install protobuf
اگر هم از ویندوز استفاده میکنید، از این طریق میتوانید نسخهی مورد نظر را به راحتی دانلود و مورد استفاده قرار بدهید:
https://github.com/google/protobuf/releases https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-win32.zip
حالا میخواهیم اولین فایل خود را با این فرمت بسازیم.
اول از همه با هم نگاهی به ساختار فایل مربوطه میاندازیم:
همانطور که در تصویر فوق میبینید، همه چیز به سادگی مشخص است؛ ورژن 3 که آخرین ورژن پروتکل بافر میباشد، آیتمی به نام MyMessage با پراپرتیهایی مشخص شده از Type بخصوص، تعریف شدهاند، تگها هم باید به ترتیب وارد شده باشند.
حالا میخواهیم بصورت واقعی protocol buffer خود را طراحی کرده و سپس از روی آن کدهای مربوطه را generate نماییم؛ به نام sample.proto بصورت زیر:
syntax = "proto3"; package helloworld; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
در فایل فوق علاوه بر تعریفهای اولیه، یک سرویس را هم اضافه کردهایم و همچنین متدی را با ورودی و خروجیهای مشخصی ایجاد کردهایم (امکانات پروتکل بافر خیلی بیشتر از این موارد است؛ از جمله فرمتهای آرایه و غیره را نیز پشتیبانی میکند، همچنین از روشی برای versioning استفاده میکند که obsolete کردن پراپرتیها و نسخه بندی را بسیار راحت میکند و ...). به سادگی قابلیت طراحی و پیاده سازی سرور و کلاینت مربوط به این آیتم ایجاد شده با استفاده از زبانهای برنامه نویسی مختلف فراهم میباشد. حال کافیاست که پروتکل بافر خود را با زبان دلخواه خود generate کنیم. در قسمت زیر برای زبانهای برنامه نویسی Go و #C، کدها را تولید میکنیم.
protoc sample.proto --go_out=plugins=grpc:.
protoc sample.proto --csharp_out=.
بعد از تولید شدن کدها با استفاده از زبان برنامه نویسی دلخواه خود میتوانید مشاهد کنید سرویس ها، تایپها و غیره همگی ساخته شدهاند و کاملا آمادهی استفاده هستند.
در مقالهی بعدی به آشنایی با gRPC میپردازیم و ضمن اینکه یک سرور با #C و یک کلاینت با زبان برنامه نویسی Go را نوشته که از طریق پروتکل بافر با هم به تبادل اطلاعات میپردازند!
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Exec Command="xcopy /y /d $(ProjectDir)Packages\*.* $(OutDir)" /> </Target>
public static class StimulSoftLicense { public static void LoadLicense(IWebHostEnvironment environment) { var contentRoot = environment.ContentRootPath; var licenseFile = System.IO.Path.Combine(contentRoot,"Reports", "license.key"); Stimulsoft.Base.StiLicense.LoadFromFile(licenseFile); } }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { StimulSoftLicense.LoadLicense(env); }
public IActionResult RequestReport(int id) { return View(); } public IActionResult LoadReportData(int id) { StiReport report = new StiReport(); report.Load(StiNetCoreHelper.MapPath(this, "Reports/Requests/RequestInfo.mrt")); var landDetailsReport = GetOwnerReportData(id); report.RegBusinessObject("Land", landDetailsReport); return StiNetCoreViewer.GetReportResult(this, report); } public IActionResult ViewerEvent() { return StiNetCoreViewer.ViewerEventResult(this); }
<div style="direction: ltr"> @Html.StiNetCoreViewer(new StiNetCoreViewerOptions() { Theme = StiViewerTheme.Office2007Silver, Appearance = { RightToLeft = true, //ShowTooltips = false, ShowTooltipsHelp = false, }, Localization = "~/Reports/fa.xml", Actions = { GetReport = "LoadReportData", ViewerEvent = "ViewerEvent", }, Toolbar = { ShowAboutButton = false, ShowOpenButton = false, ShowSaveButton = true, ShowFindButton = false, ShowEditorButton = false, ShowDesignButton = false, ShowBookmarksButton = false, ShowResourcesButton = false, ShowParametersButton = false, ShowPinToolbarButton = false }, Exports = { DefaultSettings = { ExportToPdf = { CreatorString = "SANA" } }, ShowExportDialog = false, ShowExportToDocument = false, ShowExportToExcel = false, ShowExportToExcel2007 = false, ShowExportToHtml = false, ShowExportToHtml5 = false, ShowExportToImageBmp = false, ShowExportToImageJpeg = false, ShowExportToImagePcx = false, ShowExportToImagePng = false, ShowExportToImageMetafile = false, ShowExportToImageTiff = false, ShowExportToOpenDocumentCalc = false, ShowExportToMht = false, ShowExportToOpenDocumentWriter = false, ShowExportToXps = false, ShowExportToSylk = false, ShowExportToRtf = false, ShowExportToExcelXml = false, ShowExportToText = false, ShowExportToWord2007 = false, ShowExportToImageGif = false, ShowExportToCsv = false, ShowExportToDbf = false, ShowExportToDif = false, ShowExportToImageSvg = false, ShowExportToImageSvgz = false, ShowExportToPowerPoint = false, ShowExportToXml = false, ShowExportToJson = false } }) </div>
.stiJsViewerClearAllStyles { font-family: "IRANSans" !important; } //مخصوص دیالوگ تنظیمات خروجی در صورت فعال بودن .stiJsViewerGroupPanelContainer{ direction: rtl !important; } .stiJsViewerFormButtonDefault{ direction: rtl !important; } .stiJsViewerFormButtonOver{ direction: rtl !important; } .stiJsViewerFormButtonDefault>table>tbody>tr>td{ text-align: right !important; } .stiJsViewerFormButtonOver>table>tbody>tr>td{ text-align: right !important; } .stiJsViewerFormHeader{ direction: rtl; } .stiJsViewerFormHeader>table>tbody>tr>td{ text-align: right !important; } .stiJsViewerCheckBox{ direction:rtl !important; } .stiJsViewerDropdownPanel{ direction:rtl !important; } .stiJsViewerFormContainer td.stiJsViewerClearAllStyles{ direction:rtl !important; } .stiMvcViewerReportPanel table{ direction:ltr !important; }
public class Magazine { public int MagazineId { get; set; } public string Name { get; set; } public string Publisher { get; set; } public List<Article> Articles { get; set; } } public class Article { public int ArticleId { get; set; } public string Title { get; set; } public DateTime PublishDate { get; set; } public int MagazineId { get; set; } public Author Author { get; set; } public int? AuthorId { get; set; } } public class Author { public int AuthorId { get; set; } public string Name { get; set; } public List<Article> Articles { get; set; } }
protected override void OnModelCreating (ModelBuilder modelBuilder) { modelBuilder.Entity<Magazine>().HasData(new Magazine { MagazineId = 1, Name = "DNT Magazine" }); }
migrationBuilder.InsertData( table: "Magazines", columns: new[] { "MagazineId", "Name", "Publisher" }, values: new object[] { 1, "DNT Magazine", null });
set IDENTITY_INSERT ON INSERT INTO "Magazines" ("MagazineId", "Name", "Publisher") VALUES (1, 'DNT Magazine', NULL);
modelBuilder.Entity<Magazine>() .HasData(new Magazine{ MagazineId=2, Name="This Mag" }, new Magazine{ MagazineId=3, Name="That Mag" } );
modelBuilder.Entity<Magazine>().Property(m=>m.Publisher).IsRequired();
"The seed entity for entity type 'Magazine' cannot be added because there was no value provided for the required property 'Publisher'."
public Magazine(string name, string publisher) { Name=name; Publisher=publisher; }
modelBuilder.Entity<Magazine>().HasData(new Magazine("DNT Magazine", "1105 Media"));
modelBuilder.Entity<Magazine>().HasData(new {MagazineId=1, Name="DNT Mag", Publisher="1105 Media"});
migrationBuilder.InsertData( table: "Magazines", columns: new[] { "MagazineId", "Name", "Publisher" }, values: new object[] { 1, "DNT Mag", "1105 Media" });
public class Magazine { public Magazine(string name, string publisher) { Name=name; Publisher=publisher; MagazineId=Guid.NewGuid(); } public Guid MagazineId { get; private set; } public string Name { get; private set; } public string Publisher { get; private set; } public List<Article> Articles { get; set; } }
modelBuilder.Entity<Magazine>().HasData(new Magazine("DNT Mag", "1105 Media");
var mag1=new {MagazineId= new Guid("0483b59c-f7f8-4b21-b1df-5149fb57984e"), Name="DNT Mag", Publisher="1105 Media"}; modelBuilder.Entity<Magazine>().HasData(mag1);
modelBuilder.Entity<Article>().HasData(new Article { ArticleId = 1, MagazineId = 1, Title = "EF Core 2.1 Query Types"});
var mag1=new {MagazineId= new Guid("0483b59c-f7f8-4b21-b1df-5149fb57984e"), Name="DNT Mag", Publisher="1105 Media"}; modelBuilder.Entity<Magazine>().HasData(mag1);
public class Publisher { public string Name { get; set; } public int YearFounded { get; set; } } public class Magazine { public int MagazineId { get; set; } public string Name { get; set; } public Publisher Publisher { get; set; } public List<Article> Articles { get; set; } }
modelBuilder.Entity<Magazine>().HasData (new Magazine { MagazineId = 1, Name = "DNT Magazine" }); modelBuilder.Entity<Magazine>().OwnsOne (m => m.Publisher) .HasData (new { Name = "1105 Media", YearFounded = 2006, MagazineId=1 });
migrationBuilder.InsertData( table: "Magazines", columns: new[] { "MagazineId", "Name", "Publisher_Name", "Publisher_YearFounded" }, values: new object[] { 1, "DNT Magazine", "1105 Media", 2006 });
namespace EFCoreMultipleDb.Web { public class Startup { public void Configure(IApplicationBuilder app, IHostingEnvironment env) { applyPendingMigrations(app); // ... } private static void applyPendingMigrations(IApplicationBuilder app) { var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>(); using (var scope = scopeFactory.CreateScope()) { var uow = scope.ServiceProvider.GetService<IUnitOfWork>(); uow.Migrate(); } } } }
namespace EFCoreMultipleDb.DataLayer.SQLite.Context { public class SQLiteDbContext : DbContext, IUnitOfWork { // ... public void Migrate() { this.Database.Migrate(); } } }
public static void Main(string[] args) { var host = BuildWebHost(args); using (var scope = host.Services.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService<yourDBContext>(); context.Database.Migrate(); } host.Run(); }
public class DBLog { public int DBLogId { get; set; } public string? LogLevel { get; set; } public string? EventName { get; set; } public string? Message { get; set; } public string? StackTrace { get; set; } public DateTime CreatedDate { get; set; }=DateTime.Now; }
public class DBLogger:ILogger { private bool _isDisposed; private readonly ApplicationDbContext _dbContext; public DBLogger(ApplicationDbContext dbContext) { _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) { var dblLogItem = new DBLog() { EventName = eventId.Name, LogLevel = logLevel.ToString(), Message = exception?.Message, StackTrace=exception?.StackTrace }; _dbContext.DBLogs.Add(dblLogItem); _dbContext.SaveChanges(); } public bool IsEnabled(LogLevel logLevel) { return true; } public IDisposable BeginScope<TState>(TState state) { return null; } }
حال باید یک لاگ پروایدر سفارشی را ایجاد کنیم تا بتوان یک نمونه از دیتابیس لاگر سفارشی بالا (DBLogger) را ایجاد کرد.
public class DbLoggerProvider:ILoggerProvider { private bool _isDisposed; private readonly ApplicationDbContext _dbContext; public DbLoggerProvider(ApplicationDbContext dbContext) { _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); } public ILogger CreateLogger(string categoryName) { return new DBLogger(_dbContext); } public void Dispose() { } }
همانطور که ملاحظه مینمایید، این لاگ پروایدر، از اینترفیس ILoggerProvider ارث بری کردهاست که دارای متد CreateLogger میباشد ئ این متد با شروع برنامه، یک نمونه از دیتابیس لاگر سفارشی ما را ایجاد میکند. در سازندهی این کلاس، DatabaseContext را مقدار دهی نمودهایم تا آنرا به کلاس DBLogger ارسال نماییم.
در انتها کافیست در کلاس Startup.cs این لاگ پروایدر سفارشی (DbLoggerProvider ) را صدا بزنیم.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) { . . . #region CustomLogProvider var serviceProvider = app.ApplicationServices.CreateScope().ServiceProvider; var appDbContext = serviceProvider.GetRequiredService<ApplicationDbContext>(); loggerFactory.AddProvider(new DbLoggerProvider(appDbContext)); #endregion . . .
public class DbLoggerProvider:ILoggerProvider { private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly IList<DBLog> _currentBatch = new List<DBLog>(); private readonly TimeSpan _interval = TimeSpan.FromSeconds(2); private readonly BlockingCollection<DBLog> _messageQueue = new(new ConcurrentQueue<DBLog>()); private readonly Task _outputTask; private readonly ApplicationDbContext _dbContext; private bool _isDisposed; public DbLoggerProvider(ApplicationDbContext dbContext) { _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); _outputTask = Task.Run(ProcessLogQueue); } public ILogger CreateLogger(string categoryName) { return new DBLogger(this,categoryName); } private async Task ProcessLogQueue() { while (!_cancellationTokenSource.IsCancellationRequested) { while (_messageQueue.TryTake(out var message)) { try { _currentBatch.Add(message); } catch { //cancellation token canceled or CompleteAdding called } } await SaveLogItemsAsync(_currentBatch, _cancellationTokenSource.Token); _currentBatch.Clear(); await Task.Delay(_interval, _cancellationTokenSource.Token); } } internal void AddLogItem(DBLog appLogItem) { if (!_messageQueue.IsAddingCompleted) { _messageQueue.Add(appLogItem, _cancellationTokenSource.Token); } } private async Task SaveLogItemsAsync(IList<DBLog> items, CancellationToken cancellationToken) { try { if (!items.Any()) { return; } // We need a separate context for the logger to call its SaveChanges several times, // without using the current request's context and changing its internal state. foreach (var item in items) { var addedEntry = _dbContext.DbLogs.Add(item); } await _dbContext.SaveChangesAsync(cancellationToken); } catch { // don't throw exceptions from logger } } [SuppressMessage("Microsoft.Usage", "CA1031:catch a more specific allowed exception type, or rethrow the exception", Justification = "don't throw exceptions from logger")] private void Stop() { _cancellationTokenSource.Cancel(); _messageQueue.CompleteAdding(); try { _outputTask.Wait(_interval); } catch { // don't throw exceptions from logger } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_isDisposed) { try { if (disposing) { Stop(); _messageQueue.Dispose(); _cancellationTokenSource.Dispose(); _dbContext.Dispose(); } } finally { _isDisposed = true; } } } }
public class DBLogger:ILogger { private readonly LogLevel _minLevel; private readonly DbLoggerProvider _loggerProvider; private readonly string _categoryName; public DBLogger( DbLoggerProvider loggerProvider, string categoryName ) { _loggerProvider= loggerProvider ?? throw new ArgumentNullException(nameof(loggerProvider)); _categoryName= categoryName; } public IDisposable BeginScope<TState>(TState state) { return new NoopDisposable(); } public bool IsEnabled(LogLevel logLevel) { return logLevel >= _minLevel; } public void Log<TState>( LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { if (!IsEnabled(logLevel)) { return; } if (formatter == null) { throw new ArgumentNullException(nameof(formatter)); } var message = formatter(state, exception); if (exception != null) { message = $"{message}{Environment.NewLine}{exception}"; } if (string.IsNullOrEmpty(message)) { return; } var dblLogItem = new DBLog() { EventName = eventId.Name, LogLevel = logLevel.ToString(), Message = $"{_categoryName}{Environment.NewLine}{message}", StackTrace=exception?.StackTrace }; _loggerProvider.AddLogItem(dblLogItem); } private class NoopDisposable : IDisposable { public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { } } }
در مرحلهی بعد سه پکیج زیر را باید نصب کنیم :
- Microsoft.AspNet.Identity.EntityFramework
- Microsoft.AspNet.Identity.OWIN
- Microsoft.Owin.Host.SystemWeb
<connectionStrings> <add name="IdentityDb" providerName="System.Data.SqlClient" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=IdentityDb;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;MultipleActiveResultSets=True"/> </connectionStrings>
<appSettings> <add key="webpages:Version" value="3.0.0.0" /> <add key="webpages:Enabled" value="false" /> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" /> <add key="owin:AppStartup" value="Users.IdentityConfig" /> </appSettings>
using System; using Microsoft.AspNet.Identity.EntityFramework; namespace Users.Models { public class AppUser : IdentityUser { // پروپرتیهای اضافی در اینجا } }
using System.Data.Entity; using Microsoft.AspNet.Identity.EntityFramework; using Users.Models; namespace Users.Infrastructure { public class AppIdentityDbContext : IdentityDbContext<AppUser> { public AppIdentityDbContext() : base("IdentityDb") { } static AppIdentityDbContext() { Database.SetInitializer<AppIdentityDbContext>(new IdentityDbInit()); } public static AppIdentityDbContext Create() { return new AppIdentityDbContext(); } } public class IdentityDbInit : DropCreateDatabaseIfModelChanges<AppIdentityDbContext> { protected override void Seed(AppIdentityDbContext context) { PerformInitialSetup(context); base.Seed(context); } public void PerformInitialSetup(AppIdentityDbContext context) { // initial configuration will go here } } }
کلاس UserManager حاوی متدهای بالا است. اگر دقت کنید، میبینید که تمامی متدهای بالا به کلمهی Async ختم میشوند. زیرا Asp.Net Identity تقریبا کل ویژگیهای برنامه نویسی Async را پیاده سازی کرده است و این بدین معنی است که عملیات میتوانند به صورت غیر همزمان اجرا شده و دیگر فعالیتها را بلوکه نکنند.
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Users.Models; namespace Users.Infrastructure { public class AppUserManager : UserManager<AppUser> { public AppUserManager(IUserStore<AppUser> store) : base(store) { } public static AppUserManager Create( IdentityFactoryOptions<AppUserManager> options, IOwinContext context) { AppIdentityDbContext db = context.Get<AppIdentityDbContext>(); AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db)); return manager; } } }
زمانی که Identity نیاز به وهلهای از کلاس AppUserManager داشته باشد، متد استاتیک Create را صدا خواهد زد. این عمل زمانی اتفاق میافتد که عملیاتی بر روی دادههای کاربر انجام گیرد. برای ساخت وهلهای از کلاس AppUserManager نیاز به کلاس <UserStor<AppUser دارد. کلاس <UserStore<T یک پیاده سازی از رابط <IUserStore<T توسط EF میباشد که وظیفهی آن فراهم کنندهی پیاده سازی Storage-Specific متدهای تعریف شده در کلاس User Manager (که در اینجا AppUserManager ) میباشد. برای ساخت <UserStore<T نیاز به وهلهای از کلاس AppIdentityDbContext میباشد که از طریق Owin به صورت زیر قابل دریافت است:
AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
یک پیاده سازی از رابط IOwinContext، به عنوان پارامتر به متد Create پاس داده میشود. در این پیاده سازی، یک تابع جنریک به نام Get تعریف شده که اقدام به برگشت وهله ای از اشیای ثبت شدهی در کلاس شروع Owin مینماید.
ساخت کلاس شروع Owin
اگر خاطرتان باشد یک کلید در قسمت AppSettings فایل Web.config به صورت زیر تعریف کردیم:
<add key="owin:AppStartup" value="Users.IdentityConfig" />
قسمت Value کلید بالا از دو قسمت تشکیل شده است: Users بیانگر فضای نام برنامهی شماست و IdentityConfig بیانگر کلاس شروع میباشد. (البته شما میتوانید هر نام دلخواهی را برای کلاس شروع بگذارید. فقط دقت داشته باشید که نام کلاس شروع و مقدار، با کلیدی که تعریف میکنید یکی باشد)
Owin مستقل از ASP.NET اعلام شده است و قراردادهای خاص خودش را دارد. یکی از این قراردادها تعریف یک کلاس و وهله سازی آن به منظور بارگذاری و پیکربندی میان افزار و انجام دیگر کارهای پیکربندی که نیاز است، میباشد. به طور پیش فرض این کلاس Start نام دارد و در پوشهی App_Start تعریف میشود. این کلاس حاوی یک متد به نام Configuration میباشد که بوسیله زیرساخت Owin فراخوانی میشود و یک پیاده سازی از رابط Owin.IAppBuilder به عنوان آرگومان به آن پاس داده میشود که کار پشتیبانی از Setup میان افزار مورد نیاز برنامه را برعهده دارد.
using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Owin; using Users.Infrastructure; namespace Users { public class IdentityConfig { public void Configuration(IAppBuilder app) { app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create); app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), }); } } }
رابط IAppBuilder بوسیله تعدادی متد الحاقی که در کلاسهایی که در فضای نام Owin تعریف شدهاند، تکمیل شده است. متد CreatePerOwinContext کار ساخت وهلهای از کلاس AppUserManager و کلاس AppIdentityDbContext را برای هر درخواست بر عهده دارد. این مورد تضمین میکند که هر درخواست، به یک دادهی تمیز از Asp.Net Identity دسترسی خواهد داشت و نگران اعمال همزمانی و یا کش ضعیف داده نخواهد بود. متد UseCookieAuthentication به Asp.Net Identity میگوید که چگونه از کوکیها برای تصدیق هویت کاربران استفاده کند که Optionهای آن از طریق کلاس CookieAuthenticationOptions مشخص میشود. مهمترین قسمت در اینجا پروپرتی LoginPath میباشد و مشخص میکند که کلاینتهای تصدیق هویت نشده، هنگام دسترسی به یک منبع محافظت شده، به کدام URL هدایت شوند که توسط یک رشته به متد PathString پاس داده میشود.
خوب دوستان برپایی سیستم Identity به پایان رسید. انشالله در قسمت بعدی به چگونگی استفادهی از این سیستم خواهیم پرداخت.