[HttpPost, AjaxOnly, ValidateAntiForgeryToken, ValidateModelState] public virtual async Task<ActionResult> Create([Bind(Prefix = "Model")]MeetingCreateModel model) { await _service.CreateAsync(model).ConfigureAwait(false); return InformationNotification("Messages.Save.Success"); }
سلام
گزارش مستر دیتیل را در Ef برای پوکوهای virstul collection<detail> details چه طوری پیاده سازی باید کرد؟
برای مثال :
public class Master (){ public int Id {get; set;} public string Name {get; set;} public Icollection<Detail>Details {get; st;} } public Class detail(){ public int code {get; set;} public string datadetail {get; set;} }
اگر یک IEnumerable<Master> d داشته باشیم. مستر دیتایل و اتصال اونها به stimulsoft چطوری انجام میشه؟
در گزارشات Crosstab، ردیفهای یک گزارش، تبدیل به ستونهای آن میشوند؛ به همین جهت به آنها Pivot tables هم میگویند.
برای مثال فرض کنید که قصد دارید گزارش تعداد ساعت کارکرد را به ازای هر پروژه در طول چند ماه تعیین کنید. گزارش متداول از این نوع اطلاعات، یک لیست بلند بالای بیمفهوم است. این گزارش تشکیل شده از صدها رکورد به ازای کارکنان مختلف در پروژههای مختلف و ... هیچ ارزش آماری خاصی ندارد. یک گزارش بدوی است. زمانیکه این گزارش را تبدیل به حالت crosstab میکنیم، اولین ستون فقط یک شماره پروژه خواهد بود و ستونهای بعدی، مثلا نام ماهها و مقادیر آنها هم جمع کارکرد افراد بر روی یک پروژه مشخص.
مثال اول) تهیه گزارش Crosstab جمع هزینههای واحدهای مختلف به تفکیک ماه
کلاس هزینههای زیر را در نظر بگیرید که به کمک آن میتوان به ازای هر واحد یا دپارتمان در تاریخهای متفاوت، هزینهای را مشخص ساخت:
using System;
namespace Pivot.Sample1
{
public class Expense
{
public DateTime Date { set; get; }
public string Department { set; get; }
public decimal Expenses { set; get; }
}
}
با توجه به این کلاس، یک منبع داده آزمایشی جهت تهیه گزارشات، میتواند به صورت زیر باشد:
using System;
using System.Collections.Generic;
namespace Pivot.Sample1
{
public class ExpenseDataSource
{
public static IList<Expense> ExpensesDataSource()
{
return new List<Expense>
{
new Expense { Date = new DateTime(2011,11,1), Department = "Computer", Expenses = 100 },
new Expense { Date = new DateTime(2011,11,1), Department = "Math", Expenses = 200 },
new Expense { Date = new DateTime(2011,11,1), Department = "Physics", Expenses = 150 },
new Expense { Date = new DateTime(2011,10,1), Department = "Computer", Expenses = 75 },
new Expense { Date = new DateTime(2011,10,1), Department = "Math", Expenses = 150 },
new Expense { Date = new DateTime(2011,10,1), Department = "Physics", Expenses = 130 },
new Expense { Date = new DateTime(2011,9,1), Department = "Computer", Expenses = 90 },
new Expense { Date = new DateTime(2011,9,1), Department = "Math", Expenses = 95 },
new Expense { Date = new DateTime(2011,9,1), Department = "Physics", Expenses = 100 }
};
}
}
}
و اگر این لیست را به همین شکلی که هست نمایش دهیم، خروجی زیر را خواهیم داشت:
که ... خروجی مطلوبی نیست. در اینجا ما فقط 9 رکورد داریم؛ اما در عمل به ازای هر روز، یک رکورد میتواند وجود داشته باشد و این لیست طولانی، هیچ ارزش آماری خاصی ندارد. میخواهیم سرستونهای گزارش ما مطابق جدول زیر باشند:
یعنی اگر سه ماه را در نظر بگیریم با هر تعداد رکورد، فقط سه ردیف به ازای هر ماه باید حاصل شود و ستونهای دیگر هم نام بخشها یا واحدهای موجود باشند.
برای رسیدن به این خروجی Crosstab، میتوان کوئری LINQ زیر را به کمک امکانات گروه بندی اطلاعات آن تهیه کرد:
using System.Collections;
using System.Linq;
namespace Pivot.Sample1
{
public class PivotTable
{
public static IList ExpensesCrossTab()
{
return ExpenseDataSource
.ExpensesDataSource()
.GroupBy(t =>
new
{
Year = t.Date.Year,
Month = t.Date.Month
})
.Select(myGroup =>
new
{
//Year = myGroup.Key.Year,
Month = myGroup.Key.Month,
ComputerDepartment = myGroup.Where(x => x.Department == "Computer").Sum(x => x.Expenses),
MathDepartment = myGroup.Where(x => x.Department == "Math").Sum(x => x.Expenses),
PhysicsDepartment = myGroup.Where(x => x.Department == "Physics").Sum(x => x.Expenses)
})
.ToList();
}
}
}
که اینبار خروجی زیر را تولید میکند.
اگر علاقمند باشید که مثال فوق را در برنامهی LINQPad آزمایش کنید، این فایل را دریافت نموده و در آن برنامه باز نمائید.
مثال دوم) تهیه لیست Crosstab حضور و غیاب افراد در طول یک هفته
کلاس StudentStat را جهت ثبت اطلاعات حضور یک دانشجو، میتوان به شکل زیر تعریف کرد:
using System;
namespace Pivot.Sample2
{
public class StudentStat
{
public int Id { set; get; }
public string Name { set; get; }
public DateTime Date { set; get; }
public bool IsPresent { set; get; }
}
}
و بر همین اساس یک منبع داده فرضی جهت انجام گزارشات میتواند به نحو زیر تهیه شود:
using System;
using System.Collections.Generic;
namespace Pivot.Sample2
{
public class StudentsStatDataSource
{
public static IList<StudentStat> CreateMonthlyReportDataSource()
{
var result = new List<StudentStat>();
var rnd = new Random();
for (int day = 1; day < 6; day++)
{
for (int student = 1; student < 6; student++)
{
result.Add(new StudentStat
{
Id = student,
Date = new DateTime(2011, 11, day),
IsPresent = rnd.Next(-1, 1) == 0 ? true : false,
Name = "student " + student
});
}
}
return result;
}
}
}
خروجی این گزارش هم در این حالت ساده با 5 دانشجو و فقط 5 روز، 25 رکورد خواهد بود:
که ... این هم آنچنان از لحاظ آماری مطلوب و مفهوم نیست. میخواهیم سطرهای این گزارش همانند لیست واقعی حضورغیاب، فقط از نام افراد تشکیل شود و همچنین ستونها مثلا شماره یا نام روزهای یک هفته یا ماه باشند. مثلا به شکل زیر:
برای رسیدن به این خروجی Crosstab، مثلا میتوان از کوئری LINQ زیر کمک گرفت که بر اساس شماره دانشجویی اطلاعات را گروه بندی کرده است:
using System.Collections;
using System.Linq;
namespace Pivot.Sample2
{
public class PivotTable
{
public static IList StudentsStatCrossTab()
{
return StudentsStatDataSource
.CreateWeeklyReportDataSource()
.GroupBy(x =>
new
{
x.Id
})
.Select(myGroup =>
new
{
myGroup.Key.Id,
Name = myGroup.First().Name,
Day1IsPresent = myGroup.Where(x => x.Date.Day == 1).First().IsPresent,
Day2IsPresent = myGroup.Where(x => x.Date.Day == 2).First().IsPresent,
Day3IsPresent = myGroup.Where(x => x.Date.Day == 3).First().IsPresent,
Day4IsPresent = myGroup.Where(x => x.Date.Day == 4).First().IsPresent,
Day5IsPresent = myGroup.Where(x => x.Date.Day == 5).First().IsPresent,
PresentsCount = myGroup.Where(x => x.IsPresent).Count(),
AbsentsCount = myGroup.Where(x => !x.IsPresent).Count()
})
.ToList();
}
}
}
و این کوئری خروجی زیر را تولید میکند که از هر لحاظ نسبت به لیست قبلی مفهومتر است:
فایل LINQPad این مثال را میتوانید از اینجا دریافت کنید.
ما در AngularJs آبجکتی را به نام q$ داریم که برای اجرای توابع به صورت async مفید است و همچنین در استفاده از مقادیر برگشتی از این درخواستها برای پردازشهای آینده به ما کمک میکند. برای اطلاعات بیشتر در مورد این سرویس به اینجا مراجعه کنید.
در ادامه ما از تابع ()all از q$ برای ترکیب چند شیء promise داخل یک شیء promise، به منظور صدا زدن چند سرویس به صورت یکجا، استفاده میکنیم.
پیاده سازی ASP.NET Web API
قدم اول : Visual Studio را بازکنید و یک پروژه empty ASP.NET Web API را مطابق شکل زیر ایجاد کنید.
using System.Collections.Generic; namespace NG_Combine_Multiple_Promises.Models { public class Courses { public int CourseId { get; set; } public string CourseName { get; set; } } public class CourseDatabase : List<Courses> { public CourseDatabase() { Add(new Courses() { CourseId=1,CourseName="الکترونیک"}); Add(new Courses() { CourseId = 2, CourseName = "ریاضی 2" }); Add(new Courses() { CourseId = 3, CourseName = "طراحی نرم افزار" }); } } public class Student { public int StudentId { get; set; } public string Name { get; set; } public string AcadmicYear { get; set; } } public class StudentDatabase : List<Student> { public StudentDatabase() { Add(new Student() {StudentId=101,Name="محمد علوی", AcadmicYear="اول" }); Add(new Student() { StudentId = 102, Name = "طاهره موسوی", AcadmicYear = "دوم" }); Add(new Student() { StudentId = 103, Name = "علی عباسی", AcadmicYear = "سوم" }); Add(new Student() { StudentId = 104, Name = "جواد نوری", AcadmicYear = "اول" }); Add(new Student() { StudentId = 105, Name = "محسن خدایی", AcadmicYear = "دوم" }); Add(new Student() { StudentId = 106, Name = "علی کاظمی", AcadmicYear = "سوم" }); Add(new Student() { StudentId = 107, Name = "زهرا مقدم", AcadmicYear = "اول" }); Add(new Student() { StudentId = 108, Name = "لاله فکور", AcadmicYear = "دوم" }); Add(new Student() { StudentId = 109, Name = "علی نوروزی", AcadmicYear = "چهارم" }); } } }
کد بالا شامل موجودیتهای Courses و Student است و کلاسهای CourseDatabase و StudentDatabase به ترتیب برای ذخیره این موجودیتها است.
قدم سوم : بستهی نیوگت Microsoft.AspNet.WebAPi.Cors را با استفاده از NuGet Package Manager، به منظور فعال سازی امکان صدا زدن این وب سرویس از دامنههای مختلف، به پروژه اضافه کرده و کد زیر را در کلاس WebApiCofig در پوشه App_Start قرار دهید.
config.EnableCors();
قدم چهارم : دو کنترل از نوع Web API 2 Empty را با نامهای CourseAPIController و StudentAPIController ایجاد کرده و کدهای زیر را در آنها قرار دهید.
CourseAPIController.cs
[EnableCors("*","*","*")] public class CourseAPIController : ApiController { [Route("Courses")] public IEnumerable<Courses> Get() { return new CourseDatabase(); } }
StudentAPIController.cs
[EnableCors("*", "*", "*")] public class StudentAPIController : ApiController { [Route("Students")] public IEnumerable<Student> Get() { return new StudentDatabase(); } }
استفاده از Angular $q.all
قدم اول : پروژهی جدیدی را از نوع Empty ASP.NET در همین solution اضافه کرده و ارجاعات jQuery, Bootstrap و AngularJS را با استفاده از NuGet Package manager مانند زیر اضافه کنید:
Install-Package jQuery Install-Package bootstrap Install-Package angularjs
قدم دوم : پوشهایی را به نام MyScripts ایجاد کرده و درون آن فایل javascript زیر را با نام logic.js اضافه کنید:
var app = angular.module('mymodule', []); //سرویسی برای بازگرداندن لیست دروس app.service('courseService', function ($http) { this.get = function () { var response = $http.get("http://localhost:11696/Courses"); return response; }; }); //سرویسی برای بازگرداندن لیست دانشجویان app.service('studentService', function ($http) { this.get = function () { var response = $http.get("http://localhost:11696/Students"); return response; }; }); //تعریف کنترلر app.controller('ctrl', function ($scope, $q, courseService, studentService) { $scope.Courses = []; $scope.Students = []; loadData(); /**/ function loadData() { var promiseCourse = courseService.get(); var promiseStudent = studentService.get(); $scope.combineResult = $q.all([ promiseCourse, promiseStudent ]).then(function (resp) { $scope.Courses = resp[0].data; $scope.Students = resp[1].data; }); } });
<!DOCTYPE html> <html ng-app="mymodule"> <head> <title></title> <meta charset="utf-8" /> <link href="../Content/bootstrap.min.css" rel="stylesheet" /> <script src="../Scripts/jquery-3.2.1.min.js"></script> <script src="../Scripts/angular.min.js"></script> <script src="../MyScripts/logic.js"></script> </head> <body ng-controller="ctrl"> <div class="container"> <h1 class="h1">دروس</h1> <table class="table table-striped table-bordered table-condensed"> <thead> <tr> <td class="text-center">کد درس</td> <td class="text-center">نام درس</td> </tr> </thead> <tbody> <tr ng-repeat="Course in Courses"> <td class="text-center">{{Course.CourseId}}</td> <td class="text-center">{{Course.CourseName}}</td> </tr> </tbody> </table> <hr /> <h1 class="h1">دانشجویان</h1> <table class="table table-striped table-bordered table-condensed"> <thead> <tr> <td class="text-center">کد دانشجویی</td> <td class="text-center">نام و نام خانوادگی</td> <td class="text-center">سال دانشجویی</td> </tr> </thead> <tbody> <tr ng-repeat="Student in Students"> <td class="text-center">{{Student.StudentId}}</td> <td class="text-center">{{Student.Name}}</td> <td class="text-center">{{Student.AcadmicYear}}</td> </tr> </tbody> </table> </div> </body> </html>
همانطور که در قسمت اول گفتم AutoMapper کارش رو بر اساس قراردادها انجام میده یا همون Convention Base. یکی از قردادهای AutoMapper، نگاشت براساس نام اعضای اون شی هست؛ مثلا در مثال قبلی FirstName در مبداء، به خاصیتی با همین نام نگاشت شد و ...
Projection
روش استفاده بعدی که به اون Projection (معنی فارسی خوب برا Projection چیه ؟) میگن برای مواقعی هست که اعضای یک شیء در مبداء، همتایی در مقصد ندارد (از نظر نام) و در واقع میخواهیم یک شیء رو به یک شیء دیگه تغییر شکل بدیم.
مدل زیر رو در نظر بگیرید:
public class Person { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
که قرار است به مدل PersonDTO نگاشت بشه.
public class PersonDTO { public int Id { get; set; } public string Name { get; set; } public string Family { get; set; } public string FullName { get; set; } public string Email { get; set; } }
Mapper.CreateMap<Person, PersonDTO>().ForMember(des => des.Name, op => op.MapFrom(src => src.FirstName)). ForMember(des => des.FullName, op => op.MapFrom(src => src.FirstName + " " + src.LastName)).ForMember( des => des.Email, op => op.Ignore());
AutoMapper.IMappingExpression<TSource,TDestination>.ForMember(System.Linq.Expressions.Expression<System.Func<TDestination,object>>, System.Action<AutoMapper.IMemberConfigurationExpression<TSource>>)
نکته:برای تست کردن اینکه آیا نگاشت ما Exception داره یا نه میتوان از متد زیر استفاده کرد.
Mapper.AssertConfigurationIsValid();
نکته: همیشه موقع نگاشت، اعضای شیء مقصد برای AutoMapper مهمن؛ مثلا در مثال بالا خاصیت LastName در مبداء به هیچ عضوی در مقصد نگاشت نشده (به صورت مستقیم) و این تولید Exception نمیکنه ولی برعکس اون باعث تولید Exception میشه؛ مثلا اگه خاصیت Email رو که در مبداء همتایی نداره رو کانفیگ نکنیم، باعث تولید Exception میشه.
نحوه استفاده
var person = new Person { Id = 1, FirstName = "Mohammad", LastName = "Saheb", }; var personDTO = Mapper.Map<Person, PersonDTO>(person);
Collection
بعد از نوشتن کانفیگ نگاشتها، بدون نیاز به تنظیمات خاصی میتونیم مجموعه ای از شیهای مبداء رو به مقصد نگاشت کنیم. مجموعههای پشتیبانی شده به شرح زیرن.
IEnumerable, IEnumerable<T>, ICollection, ICollection<T>, IList, IList<T>, List<T>.
مثال
var persons = new[] { new Person { Id = 1, FirstName = "Mohammad", LastName = "Saheb" }, new Person { Id = 2, FirstName = "Babak", LastName = "Saheb" } }; var personDTOs = Mapper.Map<Person[], List<PersonDTO>>(persons);
Nested Mappings
برای نگاشت کلاسهای تو در تو از این روش استفاده میکنیم و ...
فرض کنید در کلاس Person خاصیتی از نوع Address داریم و در کلاس PersonDTO خاصیتی از نوع AddressDTO.
public class Address { public string Ad1 { get; set; } public string Ad2 { get; set; } } public class AddressDTO { public string Ad1 { get; set; } public string Ad2 { get; set; } }
Mapper.CreateMap<Address, AddressDTO>();
ادامه دارد...
HttpContext و امکان دسترسی به Service Locatorها
در ASP.NET Core هر جائیکه دسترسی به HttpContext وجود داشته باشد، میتوان از الگوی Service Locator نیز توسط خاصیت HttpContext.RequestServices آن استفاده کرد. این خاصیت از نوع IServiceProvider قرار گرفتهی در فضای نام System است که در قسمت دوم آنرا بررسی کردیم. توسط این اینترفیس به متد object GetService(Type serviceType) دسترسی خواهیم یافت و برای کار با نگارشهای جنریک آن نیاز است فضای نام Microsoft.Extensions.DependencyInjection را مورد استفاده قرار داد:
using Microsoft.Extensions.DependencyInjection; namespace CoreIocSample02.Controllers { public class HomeController : Controller { public IActionResult Privacy() { var myDisposableService = this.HttpContext.RequestServices.GetRequiredService<IMyDisposableService>(); myDisposableService.Run(); return View(); } } }
استفاده از Service Locatorها در فیلترها
هرچند استفادهی از this.HttpContext.RequestServices در یک اکشن متد که کنترلر آن تزریق وابستگیهای در سازندهی کلاس را به صورت توکار پشتیبانی میکند، مزیت خاصی را به همراه ندارد و توصیه نمیشود، اما در انتهای قسمت قبل، امکان تزریق وابستگیهای متداول در فیلترها را نیز بررسی کردیم. زمانیکه کار تزریق وابستگیها در سازندهی یک فیلتر صورت میگیرد، دیگر نمیتوان ApiExceptionFilter را به نحو متداول [ApiExceptionFilter] فراخوانی کرد؛ چون پارامترهای سازندهی آن جزو ثوابت قابل کامپایل نیستند و کامپایلر سیشارپ چنین اجازهای را نمیدهد. به همین جهت مجبور به استفادهی از [ServiceFilter(typeof(ApiExceptionFilter))] برای معرفی یک چنین فیلترهایی هستیم. اما میتوان این وضعیت را با استفاده از الگوی Service Locator بهبود بخشید. اینبار بجای تعریف وابستگیها در سازندهی یک فیلتر:
public class ApiExceptionFilter : ExceptionFilterAttribute { private ILogger<ApiExceptionFilter> _logger; private IHostingEnvironment _environment; private IConfiguration _configuration; public ApiExceptionFilter(IHostingEnvironment environment, IConfiguration configuration, ILogger<ApiExceptionFilter> logger) { _environment = environment; _configuration = configuration; _logger = logger; }
using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; namespace Filters { public class ApiExceptionFilter : ExceptionFilterAttribute { public override void OnException(ExceptionContext context) { var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<ApiExceptionFilter>>(); logger.LogError(context.Exception, context.Exception.Message); base.OnException(context); } } }
استفاده از Service Locatorها در ValidationAttributes
روش تزریق وابستگیها در سازندهی کلاسهای ValidationAttribute مهیا نیست و امکانی مانند ServiceFilterها در اینجا کار نمیکند. به همین جهت تنها روشی که برای دسترسی به سرویسها باقی میماند استفاده از الگوی Service Locator است که مثالی از آنرا در کدهای زیر از طریق ValidationContext مشاهده میکنید:
using Microsoft.Extensions.DependencyInjection; using System.ComponentModel.DataAnnotations; using CoreIocServices; namespace Test { public class CustomValidationAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var service = validationContext.GetRequiredService<IMyDisposableService>(); // use service // ... validation logic } } }
استفاده از Service Locatorها در متد Main کلاس Program
فرض کنید سرویسی را در متد ConfigureServices کلاس Startup یک برنامهی وب ثبت کردهاید:
namespace Test { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSingleton<ITokenGenerator, TokenGenerator>(); }
namespace Test { public class Program { public static void Main(string[] args) { IWebHost webHost = CreateWebHostBuilder(args).Build(); var tokenGenerator = webHost.Services.GetRequiredService<ITokenGenerator>(); string token = tokenGenerator.GetToken(); System.Console.WriteLine(token); webHost.Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>(); } }
namespace Microsoft.AspNetCore.Hosting { public interface IWebHost : IDisposable { IServiceProvider Services { get; } } }
استفاده از Service Locatorها در متد ConfigureServices کلاس Startup
برای دسترسی به سرویسهای برنامه در متد ConfigureServices میتوان متد BuildServiceProvider را بر روی پارامتر services فراخوانی کرد. خروجی آن از نوع کلاس ServiceProvider است که امکان دسترسی به متدهایی مانند GetRequiredService را میسر میکند:
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { var scopeFactory = services.BuildServiceProvider().GetRequiredService<IServiceScopeFactory>(); using (var scope = scopeFactory.CreateScope()) { var provider = scope.ServiceProvider; using (var dbContext = provider.GetRequiredService<ApplicationDbContext>()) { // ... } } }
استفاده از Service Locatorها در متد Configure کلاس Startup
در قسمت قبل عنوان شد که میتوان سرویسهای مدنظر خود را به صورت پارامترهایی جدید به متد Configure اضافه کرد و کار وهله سازی آنها توسط Service Provider برنامه به صورت خودکار صورت میگیرد:
public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app, IAmACustomService customService) { // .... } }
namespace CoreIocSample02 { public class Startup { public void Configure(IApplicationBuilder app, IHostingEnvironment env) { var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>(); using (var scope = scopeFactory.CreateScope()) { var provider = scope.ServiceProvider; using (var dbContext = provider.GetRequiredService<ApplicationDbContext>()) { //... } }
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class SanitizeInputAttribute : ActionFilterAttribute { var sanitizer = new Ganss.XSS.HtmlSanitizer(); public override void OnActionExecuting(ActionExecutingContext filterContext) { if (filterContext.ActionArguments != null) { foreach (var parameter in filterContext.ActionArguments) { var properties = parameter.Value.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public) .Where(x => x.CanRead && x.CanWrite && x.PropertyType == typeof(string) && x.GetGetMethod(true).IsPublic && x.GetSetMethod(true).IsPublic); foreach (var propertyInfo in properties) { if (propertyInfo.GetValue(parameter.Value) != null) propertyInfo.SetValue(parameter.Value, sanitizer.Sanitize(propertyInfo.GetValue(parameter.Value).ToString())); } } } } }
[SanitizeInput] public IActionResult Add(GroupDto dto)
افزودن وابستگیهای MailKit به برنامه
برای شروع به استفادهی از MailKit، میتوان بستهی نیوگت آنرا به فایل project.json برنامه معرفی کرد:
{ "dependencies": { "MailKit": "1.10.0" } }
استفاده از MailKit جهت تکمیل وابستگیهای ASP.NET Core Identity
قسمتی از ASP.NET Core Identity، شامل ارسال ایمیلهای «ایمیل خود را تائید کنید» است که آنرا میتوان توسط MailKit به نحو ذیل تکمیل کرد:
using System.Threading.Tasks; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using MailKit.Net.Smtp; using MailKit.Security; using MimeKit; namespace ASPNETCoreIdentitySample.Services.Identity { public class AuthMessageSender : IEmailSender, ISmsSender { public async Task SendEmailAsync(string email, string subject, string message) { var emailMessage = new MimeMessage(); emailMessage.From.Add(new MailboxAddress("DNT", "do-not-reply@dotnettips.info")); emailMessage.To.Add(new MailboxAddress("", email)); emailMessage.Subject = subject; emailMessage.Body = new TextPart(TextFormat.Html) { Text = message }; using (var client = new SmtpClient()) { client.LocalDomain = "dotnettips.info"; await client.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false); await client.SendAsync(emailMessage).ConfigureAwait(false); await client.DisconnectAsync(true).ConfigureAwait(false); } } public Task SendSmsAsync(string number, string message) { // Plug in your SMS service here to send a text message. return Task.FromResult(0); } } }
در آخر، این پیام به SmtpClient جهت ارسال نهایی، فرستاده میشود. این SmtpClient هرچند هم نام مشابه آن در System.Net.Mail است اما با آن یکی نیست و متعلق است به MailKit. در اینجا ابتدا LocalDomain تنظیم شدهاست. تنظیم این مورد اختیاری بوده و صرفا به SMTP سرور دریافت کنندهی ایمیلها، مرتبط است که آیا قید آنرا اجباری کردهاست یا خیر. تنظیمات اصلی SMTP Server در متد ConnectAsync ذکر میشوند که شامل مقادیر host ،port و پروتکل ارسالی هستند.
ارسال ایمیل به SMTP pickup folder
روشی که تا به اینجا بررسی شد، جهت ارسال ایمیلها به یک SMTP Server واقعی کاربرد دارد. اما در حین توسعهی محلی برنامه میتوان ایمیلها را در داخل یک پوشهی موقتی ذخیره و آنها را توسط برنامهی Outlook (و یا حتی مرورگر Firefox) بررسی و بازبینی کامل کرد.
در این حالت تنها کاری را که باید انجام داد، جایگزین کردن قسمت ارسال ایمیل واقعی توسط SmtpClient در کدهای فوق، با قطعه کد ذیل است:
using (var stream = new FileStream($@"c:\smtppickup\email-{Guid.NewGuid().ToString("N")}.eml", FileMode.CreateNew)) { emailMessage.WriteTo(stream); }
FAQ و منبع تکمیلی
modelBuilder.Entity<BlogComment>() .HasOne(x => x.Reply) .WithMany(x => x.Children) .HasForeignKey(x => x.ReplyId) .OnDelete(DeleteBehavior.SetNull);