var resultBytes = JsonSerializer.SerializeToUtf8Bytes(data, new JsonSerializerOptions { WriteIndented = false, IgnoreNullValues = true });
var data = JsonSerializer.Deserialize<T>(new ReadOnlySpan<byte>(resultBytes));
var resultBytes = JsonSerializer.SerializeToUtf8Bytes(data, new JsonSerializerOptions { WriteIndented = false, IgnoreNullValues = true });
var data = JsonSerializer.Deserialize<T>(new ReadOnlySpan<byte>(resultBytes));
"address":{ "street":"test 59", "city":"City test", "post_number":"25050" },
columns : [ { field: "address.street", title: "Street" }, { field: "address.city", title: "City" }, { field: "address.post_number", title: "Post#" } ]
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
</head>
<body>
@RenderBody()
</body>
</html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="shortcut icon" href="@Url.Content("~/favicon.ico")" type="image/x-icon" />
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
</head>
<body>
<div id="menu">
@if (IsSectionDefined("Menu"))
{
RenderSection("Menu", required: false);
}
else
{
<span>This is the default ...!</span>
}
</div>
<div id="body">
@RenderBody()
</div>
</body>
</html>
@{
ViewBag.Title = "Index";
//Layout = null;
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
Index</h2>
@section Menu{
<ul>
<li>item 1</li>
<li>item 2</li>
</ul>
}
@section JavaScript
{
<script type="text/javascript" src="@Url.Content("~/Scripts/SomeScript.js")" />;
<script type="text/javascript" src="@Url.Content("~/Scripts/AnotherScript.js")" />;
}
@RenderSection("JavaScript", required: false)
using System.Web.Mvc;
namespace MvcApplication11.Areas.Reporting
{
public class ReportingAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Reporting";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Reporting_default",
"Reporting/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
}
Multiple types were found that match the controller named 'Home'. This can happen if the route that services this
request ('{controller}/{action}/{id}') does not specify namespaces to search for a controller that matches the request.
If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.
The request for 'Home' has found the following matching controllers:
MvcApplication11.Areas.Admin.Controllers.HomeController
MvcApplication11.Controllers.HomeController
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
, namespaces: new[] { "MvcApplication11.Controllers" }
);
}
@Html.ActionLink(linkText: "Home", actionName: "Index", controllerName: "Home")
@Html.ActionLink(linkText: "Home", actionName: "Index", controllerName: "Home",
routeValues: new { Area = "Admin" } , htmlAttributes: null)
public ActionResult Index()
{
return RedirectToAction("Index", "Home", new { Area = "Admin" });
}
namespace BlazorWasm.WebApi { public class Startup { //... public void ConfigureServices(IServiceCollection services) { services.AddAutoMapper(typeof(MappingProfile).Assembly); services.AddScoped<IHotelRoomService, HotelRoomService>(); services.AddScoped<IAmenityService, AmenityService>(); services.AddScoped<IHotelRoomImageService, HotelRoomImageService>(); var connectionString = Configuration.GetConnectionString("DefaultConnection"); services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); services.AddIdentity<IdentityUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); //...
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\BlazorServer\BlazorServer.DataAccess\BlazorServer.DataAccess.csproj" /> <ProjectReference Include="..\..\BlazorServer\BlazorServer.Services\BlazorServer.Services.csproj" /> <ProjectReference Include="..\..\BlazorServer\BlazorServer.Models.Mappings\BlazorServer.Models.Mappings.csproj" /> </ItemGroup> </Project>
{ "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=HotelManagement;Trusted_Connection=True;MultipleActiveResultSets=true" } }
using BlazorServer.Models; using BlazorServer.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Threading.Tasks; namespace BlazorWasm.WebApi.Controllers { [Route("api/[controller]")] public class HotelRoomController : ControllerBase { private readonly IHotelRoomService _hotelRoomService; public HotelRoomController(IHotelRoomService hotelRoomService) { _hotelRoomService = hotelRoomService; } [HttpGet] public IAsyncEnumerable<HotelRoomDTO> GetHotelRooms() { return _hotelRoomService.GetAllHotelRoomsAsync(); } [HttpGet("{roomId}")] public async Task<IActionResult> GetHotelRoom(int? roomId) { if (roomId == null) { return BadRequest(new ErrorModel { Title = "", ErrorMessage = "Invalid Room Id", StatusCode = StatusCodes.Status400BadRequest }); } var roomDetails = await _hotelRoomService.GetHotelRoomAsync(roomId.Value); if (roomDetails == null) { return BadRequest(new ErrorModel { Title = "", ErrorMessage = "Invalid Room Id", StatusCode = StatusCodes.Status404NotFound }); } return Ok(roomDetails); } } }
namespace BlazorServer.Models { public class ErrorModel { public string Title { get; set; } public int StatusCode { get; set; } public string ErrorMessage { get; set; } } }
namespace BlazorWasm.WebApi { public class Startup { public void ConfigureServices(IServiceCollection services) { // ... services.AddCors(o => o.AddPolicy("HotelManagement", builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); })); services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; // To avoid `JsonSerializationException: Self referencing loop detected error` options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve; }); // ... } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... app.UseCors("HotelManagement"); app.UseRouting(); app.UseAuthentication(); // ... } } }
var reflectTypeDescriptionProvider = typeof(PropertyDescriptor).Module.GetType("System.ComponentModel.ReflectTypeDescriptionProvider"); var propertyCacheField = reflectTypeDescriptionProvider.GetField("_propertyCache", BindingFlags.Static | BindingFlags.NonPublic);
namespace WpfOneTime.Models { public class User { public string Name { set; get; } } }
using WpfOneTime.Models; using System.Collections.Generic; namespace WpfOneTime.ViewModels { public class MainWindowViewModel { public IList<User> Users { set; get; } public MainWindowViewModel() { Users = new List<User>(); for (int i = 0; i < 1000; i++) { Users.Add(new User { Name = "name " + i }); } } } }
<Window x:Class="WpfOneTime.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ViewModels="clr-namespace:WpfOneTime.ViewModels" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <ViewModels:MainWindowViewModel x:Key="vmMainWindowViewModel" /> </Window.Resources> <Grid DataContext="{Binding Source={StaticResource vmMainWindowViewModel}}"> <ListBox ItemsSource="{Binding Users}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </Window>
<TextBlock Text="{Binding Name, Mode=OneTime}" />
new ReflectPropertyDescriptorWindow().Show();
<script src="~/scripts/jquery.filedrop.js" type="text/javascript"></script>
<div id="dropZone">فایل برنامه را به داخل این کادر بکشانید</div> <br> فایل یا فایلهای آپلود شده: <ul id="uploadResult"></ul>
.files { min-height: 42px; background: #CCC none repeat scroll 0% 0%; border-top: 1px solid #FFF; margin: 11px 0px; padding: 11px 13px; border-radius: 6px; } #dropZone.mouse-over { background-color: #1d4257; }
$('#dropZone').filedrop({ url: uploadAddress, paramname: 'files', maxFiles: 1, dragOver: function() { $('#dropZone').addClass('mouse-over'); }, dragLeave: function() { $('#dropZone').removeClass('mouse-over'); }, drop: function() { $('#dropZone').removeClass('mouse-over'); }, afterAll: function() { $('#dropZone').html('آپلود با موفقیت انجام شد'); }, uploadFinished: function(i, file, response, time) { $('#uploadResult').append('<li>' + file.name + '</li>'); } });
Url | آدرسی که قرار است فایلها به آن سمت ارسال شوند. |
Paramname | در سمت سرور باید فایلها را با استفاده از این نام پارامتر دریافت کنید. |
maxFiles | تعداد فایلهایی که میتوان با درگ و دراپ کردن روی آن به دست آورد. در بالا به یک فایل محدود شده است. |
dragOver | این رویداد زمانی اجرا خواهد شد که اشاره گر با حالت درگ کرده فایلها را به محل آپلود آورده است. |
dragLeave | موقعی که ماوس از محل آپلود خارج میشود |
drop | موقعی که شما فایلها را روی محل آپلود رها میکنید. |
afterAll | بعد از اینکه همه کارها تمام شد اجرا میشود.(آخرین رویداد) |
uploadFinished | کار آپلود به پایان رسیده است. در مثال بالا پس از پایان آپلود، نام فایل آپلود شده را به کاربر نشان دادهایم. |
نحوهی دریافت آن در سمت سرور, در یک اکشن متد به صورت زیر است:
[HttpPost] public virtual ActionResult UpdateApp(IEnumerable<HttpPostedFileBase>files) { foreach (HttpPostedFileBase file in files) { string filePath = Path.Combine(TempPath, file.FileName); file.SaveAs(filePath); } return Json(new {state = "success", message = "با موفقیت عملیات ارسال فایل انجام شد"}, JsonRequestBehavior.AllowGet); }
در اکشن متد بالا ما فایلها را از طریق نام پارامتر files که مشخص کرده بودیم، به عنوان یک لیست شمارشی دریافت میکنیم. کدها بالا برای سادهترین راه اندازی ممکن کفایت میکنند.
این موارد از اصلیترینها هستند که به کار میآیند. به غیر اینها یک سری خصوصیات اضافهتری هم برای آن وجود دارد.
fallback_id | اگر دوست دارید این آپلودر را نیر به یک آپلودر معمولی اتصال دهید از این شناسه استفاده کنید. |
withCredentials | با استفاده از کوکیها یک درخواست cross-origin ایجاد میکند. |
data | اگر دوست دارید به همراه فایلها اطلاعات دیگری هم به همراه آن
ارسال و پست شوند از این طریق اقدام نمایید. میتواند در قالب یک متغیر
باشد یا خروجی یک تابع.data: { param1: 'value1', param2: function(){ return calculated_data; } |
headers | برای ارسال مقدار اضافهتر در هدر درخواست به کار میرود و صدا زدن آن همانند کد data میباشد. |
error | در صورتیکه در فرایند آپلود خطایی رخ دهد، اجرا میگردد. نحوهی کدنویسی آن و بررسی خطاهای آن به شرح زیر است:error: function(err, file) { switch(err) { case 'BrowserNotSupported': alert('مرورگر از این فناوری پشتیبانی نمیکند') break; case 'TooManyFiles': // قصد آپلود همزمان فایلهای بیشتری از حد مجاز تعیین شده دارید break; case 'FileTooLarge': //حداقل حجم یکی از فایلها از حجم مجاز تعیین شده بیشتر است //برای دسترسی به نام آن فایل از کد زیر استفاده کنید //file.name break; case 'FileTypeNotAllowed': // نوع حداقل یکی از فایلها با نوعها مشخص شده ما یکی نیست break; case 'FileExtensionNotAllowed': // پسوند حداقل یکی از فایلها مورد تایید نیست break; default: break; } } |
allowedfiletypes | نوع فایلهای مجاز را تعیین میکند:allowedfiletypes: |
allowedfileextensions | پسوند فایل هایی که برای آپلود مجاز هستند را معرفی میکند.allowedfileextensions: |
maxfilesize | حداکثر حجم مجاز برای هر فایل که به مگابایت بیان میشود. |
docOver | این رویداد زمانی اجرا میشود که فایلهای درگ شده شما وارد محیط یا پنجره مرورگر میشود. |
uploadStarted | این رویداد زمانی اجرا میگردد که فرایند آپلود هر فایل به طور جداگانه در حال آغاز شدن است: متغیر i در کد زیر شامل اندیس فایلی است که آپلودش آغاز شده است و این اندیس از صفر آغاز میشود. متغیر file دسترسی شما را به اطلاعات یک فایل باز میکند مانند نام فایل. متغیر len تعداد فایل هایی را که کاربر در محل آپلود رها کرده است، باز میگرداند. function(i, file, len){ }, |
uploadFinished | با اتمام آپلود هر فایل، این رویداد فراخوانی میگردد. دو
پارامتر اول آن، همانند سابق هستند. پارامتر response خروجی json ایی را که در سمت
سرور برگرداندیم، به ما باز میگرداند. پارامتر بعدی، زمانی را که برای
آپلود طول کشیده است، بر میگرداند. function(i, file, response, time) { } |
progressUpdated | این رویداد برای نمایش پیشرفت یک آپلود مناسب است که آخرین پارامتر آن یک عدد صحیح از پیشرفت فایل را بر میگرداند.function(i, file, progress) { }, |
globalProgressUpdated | این رویداد میزان پیشرفت کلیه فایلها را به درصد باز میگرداند:function(progress) { $('#progress div') |
speedUpdated | سرعت آپلود هر فایل را با کیلوبیت بر ثانیه مشخص میکند.function(i, file, speed) { } |
rename | در صورتی که قصد تغییر نام فایل ارسالی را دارید میتوانید از این رویداد استفاده کنید. پارامتر name، نام اصلی فایل را بر میگرداند که میتوانید آن را دستکاری کنید و نام جدیدی را به عنوان خروجی برگردانید. نمونه کاربردی از این رویداد rename: function(name) { } |
beforeEach | این رویداد قبل از آپلود هر فایل آغاز میگردد و برگرداندن مقدار false در آن باعث جلوگیری و کنسل شدن آپلود آن فایل میگردد.function(file) { } |
beforeSend | پارامترهای اولی تکراری هستند ولی آخرین پارامتر یک
تابع done را میتوان به آن پاس کرد که قبل از اجرای کل عملیات آپلود صدا
زده میشود.function(file, i, done) { } |
<script src="https://rawgit.com/enyo/dropzone/master/dist/dropzone.js"></script> <form action="/upload-target" class="dropzone"></form>
Install-Package dropzone
<form action="~/Home/SaveUploadedFile" method="post" enctype="multipart/form-data" class="dropzone" id="dropzoneForm" style="width: 50px; background: none; border: none;"> <div class="fallback"> <input name="file" type="file" multiple /> <input type="submit" value="Upload" /> </div> </form>
var myDropzone = new Dropzone("div#myId", { url: "/file/post"}); //============ OR ==================== $("div#myId").dropzone({ url: "/file/post" });
Dropzone.options.myId= { paramName: "file", //نام پارامتری که فایل از طریق آن انتقال میبابد maxFilesize: 2, // MB accept: function(file, done) { if (file.name == "justinbieber.jpg") { done("Naha, you don't."); } else { done(); } } };
<configuration> <system.web> <httpRuntime maxRequestLength="1048576" /> </system.web> </configuration>
<system.webServer> <security> <requestFiltering> <requestLimits maxAllowedContentLength="1073741824" /> </requestFiltering> </security> </system.webServer>
using System;
namespace System.Web.Mvc
{
public abstract class WebViewPage<TModel> : WebViewPage
{
protected WebViewPage();
public AjaxHelper<TModel> Ajax { get; set; }
public HtmlHelper<TModel> Html { get; set; }
public TModel Model { get; }
public ViewDataDictionary<TModel> ViewData { get; set; }
public override void InitHelpers();
protected override void SetViewData(ViewDataDictionary viewData);
}
}
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
</head>
namespace MvcApplication18.Models
{
public class Employee
{
public int Id { set; get; }
public string Name { set; get; }
}
}
using System.Collections.Generic;
namespace MvcApplication18.Models
{
public static class EmployeeDataSource
{
public static IList<Employee> CreateEmployees()
{
var list = new List<Employee>();
for (int i = 0; i < 1000; i++)
{
list.Add(new Employee { Id = i + 1, Name = "name " + i });
}
return list;
}
}
}
using System.Linq;
using System.Web.Mvc;
using MvcApplication18.Models;
namespace MvcApplication18.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost] //for IE-8
public ActionResult EmployeeInfo(int? id)
{
if (!Request.IsAjaxRequest())
return View("Error");
if (!id.HasValue)
return View("Error");
var list = EmployeeDataSource.CreateEmployees();
var data = list.Where(x => x.Id == id.Value).FirstOrDefault();
if (data == null)
return View("Error");
return PartialView(viewName: "_EmployeeInfo", model: data);
}
}
}
@model MvcApplication18.Models.Employee
<strong>Name:</strong> @Model.Name
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div id="EmployeeInfo">
@Ajax.ActionLink(
linkText: "Get Employee-1 info",
actionName: "EmployeeInfo",
controllerName: "Home",
routeValues: new { id = 1 },
ajaxOptions: new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "EmployeeInfo",
LoadingElementId = "Progress"
})
</div>
<div id="Progress" style="display: none">
<img src="@Url.Content("~/Content/images/loading.gif")" alt="loading..." />
</div>
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)]
public class AjaxOnlyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
base.OnActionExecuting(filterContext);
}
else
{
throw new InvalidOperationException("This operation can only be accessed via Ajax requests");
}
}
}
[AjaxOnly]
public ActionResult SomeAjaxAction()
{
return Content("Hello!");
}
<a data-ajax="true" data-ajax-loading="#Progress" data-ajax-method="POST"
data-ajax-mode="replace" data-ajax-update="#EmployeeInfo"
href="/Home/EmployeeInfo/1">Get Employee-1 info</a>
<a href="#" onclick="LoadEmployeeInfo()">Get Employee-1 info</a>
@section javascript
{
<script type="text/javascript">
function LoadEmployeeInfo() {
showProgress();
$.ajax({
type: "POST",
url: "/Home/EmployeeInfo",
data: JSON.stringify({ id: 1 }),
contentType: "application/json; charset=utf-8",
dataType: "json",
// controller is returning a simple text, not json
complete: function (xhr, status) {
var data = xhr.responseText;
if (status === 'error' || !data) {
//handleError
}
else {
$('#EmployeeInfo').html(data);
}
hideProgress();
}
});
}
function showProgress() {
$('#Progress').css("display", "block");
}
function hideProgress() {
$('#Progress').css("display", "none");
}
</script>
}
@RenderSection("javascript", required: false)
[HttpPost] //for IE-8
public ActionResult EmployeeInfoData(int? id)
{
if (!Request.IsAjaxRequest())
return Json(false);
if (!id.HasValue)
return Json(false);
var list = EmployeeDataSource.CreateEmployees();
var data = list.Where(x => x.Id == id.Value).FirstOrDefault();
if (data == null)
return Json(false);
return Json(data);
}
<a href="#" onclick="LoadEmployeeInfoData()">Get Employee-2 info</a>
@section javascript
{
<script type="text/javascript">
function LoadEmployeeInfoData() {
showProgress();
$.ajax({
type: "POST",
url: "/Home/EmployeeInfoData",
data: JSON.stringify({ id: 1 }),
contentType: "application/json; charset=utf-8",
dataType: "json",
// controller is returning the json data
success: function (result) {
if (result) {
alert(result.Id + ' - ' + result.Name);
}
hideProgress();
},
error: function (result) {
alert(result.status + ' ' + result.statusText);
hideProgress();
}
});
}
function showProgress() {
$('#Progress').css("display", "block");
}
function hideProgress() {
$('#Progress').css("display", "none");
}
</script>
}
[HttpPost] //for IE-8
public ActionResult SearchEmployeeInfo(string data)
{
if (!Request.IsAjaxRequest())
return Content(string.Empty);
if (string.IsNullOrWhiteSpace(data))
return Content(string.Empty);
var employeesList = EmployeeDataSource.CreateEmployees();
var list = employeesList.Where(x => x.Name.Contains(data)).ToList();
if (list == null || !list.Any())
return Content(string.Empty);
return PartialView(viewName: "_SearchEmployeeInfo", model: list);
}
@model IEnumerable<MvcApplication18.Models.Employee>
<table>
<tr>
<th>
Name
</th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
</tr>
}
</table>
@using (Ajax.BeginForm(actionName: "SearchEmployeeInfo",
controllerName: "Home",
ajaxOptions: new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "EmployeeInfo",
LoadingElementId = "Progress"
}))
{
@Html.TextBox("data")
<input type="submit" value="Search" />
}
<form action="/Home/SearchEmployeeInfo" data-ajax="true"
data-ajax-loading="#Progress" data-ajax-method="POST"
data-ajax-mode="replace" data-ajax-update="#EmployeeInfo"
id="form0" method="post">
<input id="data" name="data" type="text" value="" />
<input type="submit" value="Search" />
</form>
$.validator.unobtrusive.parse("#{form-id}");
$.ajax
({
url: "/{controller}/{action}/{id}",
type: "get",
success: function(data)
{
$.validator.unobtrusive.parse("#{form-id}");
}
});
//or
$.get('/{controller}/{action}/{id}', function (data) { $.validator.unobtrusive.parse("#{form-id}"); });
@using (Ajax.BeginForm(
"Action1",
"Controller",
null,
new AjaxOptions {
OnSuccess = "onSuccess",
UpdateTargetId = "result"
},
null)
)
{
<input type="submit" value="Save" />
}
var onSuccess = function(result) {
// enable unobtrusive validation for the contents
// that was injected into the <div id="result"></div> node
$.validator.unobtrusive.parse("#result");
};
در مرحلهی بعد سه پکیج زیر را باید نصب کنیم :
- 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 به پایان رسید. انشالله در قسمت بعدی به چگونگی استفادهی از این سیستم خواهیم پرداخت.
در قسمت قبل به معرفی postgresql پرداختیم; در این قسمت قصد ایجاد و راه اندازی یک api با استفاده از دیتابیس postgresql و استفاده از تکنولوژیهای آن را با استفاده از docker داریم.
ابتدا با استفاده از دستور زیر یک پروژهی جدید asp.net core را ایجاد کنید:
dotnet new webapi --minimal -o YourDirectoryPath:\YourFolderName
سپس فایل docker-compose.yaml را به روت پروژه اضافه کنید که شامل کانفیگهای زیر میباشد:
version: '3.1' services: db: image: postgres container_name: db restart: always environment: POSTGRES_PASSWORD: postgres POSTGRES_USERNAME: postgres POSTGRES_DB: BloggingDb ports: - "5432:5432" volumes: - postgres_data:/data/db adminer: image: adminer restart: always ports: - 8080:8080 pgadmin4: image: dpage/pgadmin4 restart: always environment: PGADMIN_DEFAULT_EMAIL: pgadmin4@pgadmin.org PGADMIN_DEFAULT_PASSWORD: admin PGADMIN_CONFIG_SERVER_MODE: 'False' ports: - 5050:80 volumes: - pgadmin:/var/lib/pgadmin depends_on: - db volumes: postgres_data: pgadmin:
سپس با اجرای دستور زیر در روت پروژه، سرویسها را راه اندازی کنید:
docker compose up -d
معرفی سرویسهای استفاده شده در تنظیمات فایل بالا:
سرویس db :
نمونه ایمیج اصلی، volume، تنظیمات connection string در آن استفاده شده است.
سرویس adminer :
https://hub.docker.com/_/adminer /
Adminer - Database management in a single PHP file
یک برنامه تحت وب مدیریت پایگاه داده ساده میباشد که ویژگیها MySql را در کنار سرعت و امنیت ارائه میدهد و در آدرس http://localhost:8080 / اجرا خواهد شد.
سرویس pgadmin4 :
dpage/pgadmin4 - Docker Image | Docker Hub
در حال حاضر این برنامه محبوبترین برنامه مدیریت پایگاه داده میباشد که ویژگیهای پیشرفتهای را نیز پوشش میدهد و در آدرس http://localhost:5050 / اجرا خواهد شد.
اکنون نوبت نوشتن کدها میباشد.
- تنظیم connection string در فایل appsettings.json:
"ConnectionStrings": { "BloggingContext": "Username=postgres;Password=postgres;Server=localhost;Database=BloggingDb” }
- و همینطور پکیجهای زیر را به برنامه خود رفرنس دهید:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL dotnet add package Microsoft.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.Design
- مدلهای برنامه را در مسیر /Models ایجاد کنید:
namespace NpgsqlAPI.Models; public class Post { public int PostId { get; set; } public string Title { get; set; } = null!; public string Content { get; set; } = null!; public int BlogId { get; set; } public Blog Blog { get; set; } = null!; } namespace NpgsqlAPI.Models; public class Blog { public int BlogId { get; set; } public string? Url { get; set; } public List<Post>? Posts { get; set; } }
- سپس BloggingContext را در مسیر /Data ایجاد کنید:
using Microsoft.EntityFrameworkCore; using NpgsqlAPI.Models; namespace NpgsqlAPI.Data; public class BloggingContext : DbContext { public BloggingContext(DbContextOptions<BloggingContext> options) : base(options) { } public DbSet<Blog> Blogs => Set<Blog>(); public DbSet<Post
- سپس اینترفیس IBlogServices را در مسیر /Servicec/Blogs ایجاد کنید:
using NpgsqlAPI.Models; namespace NpgsqlAPI.Services.Blogs; public interface IBlogServices { Task<IEnumerable<Blog>> GetList(); Task<Blog?> Get(uint id); Task<uint> Add(Blog obj); Task AddRange(Blog[] obj); Task Update(Blog obj); Task UpdateRange(Blog[] obj); Task Remove(uint id); }
- و سپس پیاده سازی آن را در فایل BlogEFServices و در کنار اینترفیس آن قرار دهید:
using Microsoft.EntityFrameworkCore; using NpgsqlAPI.Data; using NpgsqlAPI.Models; namespace NpgsqlAPI.Services.Blogs; public sealed class BlogEFServices : IBlogServices { private readonly BloggingContext _context; public BlogEFServices(BloggingContext context) { _context = context; } public async Task<uint> Add(Blog obj) { await _context.Blogs.AddAsync(obj); return (uint)await SaveChangesAsync(); } public async Task AddRange(Blog[] obj) { await _context.Blogs.AddRangeAsync(obj); await SaveChangesAsync(); } public async Task<Blog?> Get(uint id) { return await _context.Blogs.FirstOrDefaultAsync(x=>x.BlogId == id); } public async Task<IEnumerable<Blog>> GetList() { return await _context.Blogs.ToListAsync(); } public async Task Remove(uint id) { var entity = await Get(id); _context.Blogs.Remove(entity!); await SaveChangesAsync(); } public async Task Update(Blog obj) { _context.Blogs.Update(obj); await SaveChangesAsync(); } public async Task UpdateRange(Blog[] obj) { _context.Blogs.UpdateRange(obj); await SaveChangesAsync(); } private async Task<int> SaveChangesAsync() { return await _context.SaveChangesAsync(); } }
- اکنون endpointهای api را در فایل program.cs ایجاد کنید:
using System.Data; using Microsoft.EntityFrameworkCore; using Npgsql; using NpgsqlAPI.Services.Blogs; using NpgsqlAPI.Data; using NpgsqlAPI.Models; var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); string connectionString = builder.Configuration.GetConnectionString("BloggingContext")!; builder.Services.AddDbContext<BloggingContext>(options => options.UseNpgsql(connectionString)); builder.Services.AddTransient<IDbConnection>(_ => new NpgsqlConnection(connectionString)); // builder.Services.AddScoped<IBlogServices, BlogDapperServices>(); // builder.Services.AddScoped<IBlogServices, BlogEFRawQueryServices>(); builder.Services.AddScoped<IBlogServices, BlogEFServices>(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.MapGet("/blogs", async (IBlogServices service) => await service.GetList()) .WithName("GetBlogs") .WithOpenApi(); app.MapGet("/blogs/{id}", async (IBlogServices service, uint id) => await service.Get(id)) .WithName("GetBlog") .WithOpenApi(); app.MapPost("/blogs", async (IBlogServices service, Blog blog) => await service.Add(blog)) .WithName("AddBlog") .WithOpenApi(); app.MapDelete("/blogs/{id}", async (IBlogServices service, uint id) => await service.Remove(id)) .WithName("RemoveBlog") .WithOpenApi(); app.MapPut("/blogs", async (IBlogServices service, Blog blog) => await service.Update(blog)) .WithName("UpdateBlog") .WithOpenApi(); app.MapPut("/blogs/Bulk", async (IBlogServices service, Blog[] blogs) => await service.UpdateRange(blogs)) .WithName("UpdateBulkBlog") .WithOpenApi(); app.MapPost("/blogs/Bulk", async (IBlogServices service, Blog[] blogs) => await service.AddRange(blogs)) .WithName("AddBulkBlog") .WithOpenApi(); app.Run();
تمامی کدهای برنامه تا به اینجا نوشته شدهاند. اکنون migration را پس از اطمینان از اجرا بودن داکر اجرا کنید
dotnet ef migrations add Init dotnet ef database update
و برنامه را اجرا و تست کنید.