الان به این شکل است و button انتهای تگ است که نمایش داده میشود.
الان به این شکل است و button انتهای تگ است که نمایش داده میشود.
namespace BlazorServerSample.Models { public class BlazorRoom { public int Id { set; get; } public string Name { set; get; } public decimal Price { set; get; } public bool IsActive { set; get; } } }
@using BlazorServerSample.Models
@page "/" <h2 class="bg-light border p-2"> First Room </h2> Room: @Room.Name <br/> Price: @Room.Price @code { BlazorRoom Room = new BlazorRoom { Id = 1, Name = "Room 1", IsActive = true, Price = 499 }; }
<input type="number" value="@Room.Price" />
<input type="number" @bind-value="@Room.Price" />
<input type="number" @bind-value="@Room.Price" @bind-value:event="oninput" />
<input type="checkbox" @bind-value="Room.IsActive" /> <br/> This room is @(Room.IsActive? "Active" : "Inactive").
<input type="checkbox" @bind-value="Room.IsActive" checked="@(Room.IsActive? "cheked" : null)" />
using System.Collections.Generic; namespace BlazorServerSample.Models { public class BlazorRoom { // ... public List<BlazorRoomProp> RoomProps { set; get; } } public class BlazorRoomProp { public int Id { set; get; } public string Name { set; get; } public string Value { set; get; } } }
@code { BlazorRoom Room = new BlazorRoom { Id = 1, Name = "Room 1", IsActive = true, Price = 499, RoomProps = new List<BlazorRoomProp> { new BlazorRoomProp { Id = 1, Name = "Sq Ft", Value = "100" }, new BlazorRoomProp { Id = 2, Name = "Occupancy", Value = "3" } } }; }
<select @bind="SelectedRoomPropValue"> @foreach (var prop in Room.RoomProps) { <option value="@prop.Value">@prop.Name</option> } </select> <span>The value of the selected room prop is: @SelectedRoomPropValue</span> @code { string SelectedRoomPropValue = ""; // ...
@code { string SelectedRoomPropValue = ""; List<BlazorRoom> Rooms = new List<BlazorRoom>(); protected override void OnInitialized() { base.OnInitialized(); Rooms.Add(new BlazorRoom { Id = 1, Name = "Room 1", IsActive = true, Price = 499, RoomProps = new List<BlazorRoomProp> { new BlazorRoomProp { Id = 1, Name = "Sq Ft", Value = "100" }, new BlazorRoomProp { Id = 2, Name = "Occupancy", Value = "3" } } }); Rooms.Add(new BlazorRoom { Id = 2, Name = "Room 2", IsActive = true, Price = 399, RoomProps = new List<BlazorRoomProp> { new BlazorRoomProp { Id = 1, Name = "Sq Ft", Value = "250" }, new BlazorRoomProp { Id = 2, Name = "Occupancy", Value = "4" } } }); } }
<div class="border p-2 mt-3"> <h2 class="text-info">Rooms List</h2> <table class="table table-dark"> @foreach(var room in Rooms) { <tr> <td> <input type="text" @bind-value="room.Name" @bind-value:event="oninput"/> </td> <td> <input type="text" @bind-value="room.Price" @bind-value:event="oninput"/> </td> @foreach (var roomProp in room.RoomProps) { <td> @roomProp.Name, @roomProp.Value </td> } </tr> } </table> @foreach(var room in Rooms) { <p>@room.Name's price is @room.Price.</p> } </div>
> create-react-app state-management-with-mobx-part4 > cd state-management-with-mobx-part4
> npm install --save bootstrap mobx mobx-react mobx-react-devtools mobx-state-tree
> npm install --save-dev babel-eslint customize-cra eslint eslint-config-react-app eslint-loader eslint-plugin-babel eslint-plugin-css-modules eslint-plugin-filenames eslint-plugin-flowtype eslint-plugin-import eslint-plugin-no-async-without-await eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-redux eslint-plugin-redux-saga eslint-plugin-simple-import-sort react-app-rewired typescript
const products = [ { id: 1, name: "Item 1", price: 850 }, { id: 2, name: "Item 2", price: 900 }, { id: 3, name: "Item 3", price: 1500 }, { id: 4, name: "Item 4", price: 1000 } ]; export default products;
import React from "react"; import products from "../services/productsService"; const ShopItemsList = ({ onAdd }) => { return ( <table className="table table-hover"> <thead className="thead-light"> <tr> <th>Name</th> <th>Price</th> <th>Action</th> </tr> </thead> <tbody> {products.map(product => ( <tr key={product.id}> <td>{product.name}</td> <td>{product.price}</td> <td> <button className="btn btn-sm btn-info" onClick={() => onAdd(product)} > Add </button> </td> </tr> ))} </tbody> </table> ); }; export default ShopItemsList;
import React from "react"; const BasketItemsList = ({ items, totalPrice, onRemove }) => { return ( <> <table className="table table-hover"> <thead className="thead-light"> <tr> <th>Name</th> <th>Price</th> <th>Count</th> <th>Action</th> </tr> </thead> <tbody> {items.map(item => ( <tr key={item.id}> <td>{item.name}</td> <td>{item.price}</td> <td>{item.count}</td> <td> <button className="btn btn-sm btn-danger" onClick={() => onRemove(item.id)} > Remove </button> </td> </tr> ))} <tr> <td align="right"> <strong>Total: </strong> </td> <td> <strong>{totalPrice}</strong> </td> <td></td> <td></td> </tr> </tbody> </table> </> ); }; export default BasketItemsList;
const BasketItemsList = ({ items, totalPrice, onRemove }) => {
import React, { Component } from "react"; class BasketItemsCounter extends Component { render() { const { count, onRemoveAll } = this.props; return ( <div> <h1>Total items: {count}</h1> <button type="button" className="btn btn-sm btn-danger" onClick={() => onRemoveAll()} > Empty Basket </button> </div> ); } } export default BasketItemsCounter;
import React, { Component } from "react"; import BasketItemsCounter from "./components/BasketItemsCounter"; import BasketItemsList from "./components/BasketItemsList"; import ShopItemsList from "./components/ShopItemsList"; class App extends Component { render() { return ( <main className="container"> <div className="row"> <BasketItemsCounter /> </div> <hr /> <div className="row"> <h2>Products</h2> <ShopItemsList /> </div> <div className="row"> <h2>Basket</h2> <BasketItemsList /> </div> </main> ); } } export default App;
import { action, observable } from "mobx"; export default class CounterStore { @observable totalNumbersInBasket = 0; constructor(rootStore) { this.rootStore = rootStore; } @action increase = () => { this.totalNumbersInBasket++; }; @action decrease = () => { this.totalNumbersInBasket--; }; }
import { action, computed, observable } from "mobx"; export default class MarketStore { @observable basketItems = []; constructor(rootStore) { this.rootStore = rootStore; } @action add = product => { const selectedItem = this.basketItems.find(item => item.id === product.id); if (selectedItem) { selectedItem.count++; } else { this.basketItems.push({ ...product, count: 1 }); } this.rootStore.counterStore.increase(); }; @action remove = id => { const selectedItem = this.basketItems.find(item => item.id === id); selectedItem.count--; if (selectedItem.count === 0) { this.basketItems.remove(selectedItem); } this.rootStore.counterStore.decrease(); }; @action removeAll = () => { this.basketItems = []; this.rootStore.counterStore.totalNumbersInBasket = 0; }; @computed get totalPrice() { return this.basketItems.reduce((previous, current) => { return previous + current.price * current.count; }, 0); } }
import CounterStore from "./counter"; import MarketStore from "./market"; class RootStore { counterStore = new CounterStore(this); marketStore = new MarketStore(this); } export default RootStore;
export default class MarketStore { @observable basketItems = []; constructor(rootStore) { this.rootStore = rootStore; } @action removeAll = () => { this.basketItems = []; this.rootStore.counterStore.totalNumbersInBasket = 0; }; }
import "./index.css"; import "bootstrap/dist/css/bootstrap.css"; import makeInspectable from "mobx-devtools-mst"; import { Provider } from "mobx-react"; import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; import RootStore from "./stores"; const rootStore = new RootStore(); if (process.env.NODE_ENV === "development") { makeInspectable(rootStore); // https://github.com/mobxjs/mobx-devtools } ReactDOM.render( <Provider {...rootStore}> <App /> </Provider>, document.getElementById("root") ); serviceWorker.unregister();
<Provider counterStore={rootStore.counterStore} marketStore={rootStore.marketStore}>
import { inject, observer } from "mobx-react"; import React from "react"; import products from "../services/productsService"; const ShopItemsList = ({ onAdd }) => { return ( // ... ); }; export default inject(({ marketStore }) => ({ onAdd: marketStore.add }))(observer(ShopItemsList));
import { inject, observer } from "mobx-react"; import React from "react"; const BasketItemsList = ({ items, totalPrice, onRemove }) => { return ( // ... ); }; export default inject(({ marketStore }) => ({ items: marketStore.basketItems, totalPrice: marketStore.totalPrice, onRemove: marketStore.remove }))(observer(BasketItemsList));
import { inject, observer } from "mobx-react"; import React, { Component } from "react"; @inject(rootStore => ({ count: rootStore.counterStore.totalNumbersInBasket, onRemoveAll: rootStore.marketStore.removeAll })) @observer class BasketItemsCounter extends Component { render() { const { count, onRemoveAll } = this.props; return ( // ... ); } } export default BasketItemsCounter;
namespace jQueryMvcSample03.Models { public class BlogPost { public int Id { set; get; } public string Title { set; get; } public string Body { set; get; } /// <summary> /// اطلاعات رای گیری یک مطلب به صورت یک خاصیت تو در تو یا پیچیده /// </summary> public Rating Rating { set; get; } public BlogPost() { Rating = new Rating(); } } }
namespace jQueryMvcSample03.Models { //[ComplexType] public class Rating { public double? TotalRating { get; set; } public int? TotalRaters { get; set; } public double? AverageRating { get; set; } } }
using System.Collections.Generic; using System.Linq; using jQueryMvcSample03.Models; namespace jQueryMvcSample03.DataSource { /// <summary> /// منبع داده فرضی /// </summary> public static class BlogPostDataSource { private static IList<BlogPost> _cachedItems; /// <summary> /// با توجه به استاتیک بودن سازنده کلاس، تهیه کش، پیش از سایر فراخوانیها صورت خواهد گرفت /// باید دقت داشت که این فقط یک مثال است و چنین کشی به معنای /// تهیه یک لیست برای تمام کاربران سایت است /// </summary> static BlogPostDataSource() { _cachedItems = createBlogPostsInMemoryDataSource(); } /// <summary> /// هدف صرفا تهیه یک منبع داده آزمایشی ساده تشکیل شده در حافظه است /// </summary> private static IList<BlogPost> createBlogPostsInMemoryDataSource() { var results = new List<BlogPost>(); for (int i = 1; i < 30; i++) { results.Add(new BlogPost { Id = i, Title = "عنوان " + i, Body = "متن ... متن ... متن " + i, Rating = new Rating { TotalRaters = i + 1, AverageRating = 3.5 } }); } return results; } /// <summary> /// پارامترهای شماره صفحه و تعداد رکورد به ازای یک صفحه برای صفحه بندی نیاز هستند /// شماره صفحه از یک شروع میشود /// </summary> public static IList<BlogPost> GetLatestBlogPosts(int pageNumber, int recordsPerPage = 4) { var skipRecords = pageNumber * recordsPerPage; return _cachedItems .OrderByDescending(x => x.Id) .Skip(skipRecords) .Take(recordsPerPage) .ToList(); } } }
/* star rating system */ .post_rating { direction: ltr; } .rating { text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; display: inline-block; width: 8px; height: 16px; } .rating.stars { background-image: url('Images/star_rating.png'); } .rating.stars.active { cursor: pointer; } .star-left_off { background-position: -0px -0px; } .star-left_on { background-position: -16px -0px; } .star-right_off { background-position: -8px -0px; } .star-right_on { background-position: -24px -0px; }
// <![CDATA[ (function ($) { $.fn.StarRating = function (options) { var defaults = { ratingStarsSpan: '.rating.stars', postInfoUrl: '/', loginUrl: '/login', errorHandler: null, completeHandler: null, onlyOneTimeHandler: null }; var options = $.extend(defaults, options); return this.each(function () { var ratingStars = $(this); $(ratingStars).unbind('mouseover'); $(ratingStars).mouseover(function () { var span = $(this).parent("span"); var newRating = $(this).attr("value"); setRating(span, newRating); }); $(ratingStars).unbind('mouseout'); $(ratingStars).mouseout(function () { var span = $(this).parent("span"); var rating = span.attr("rating"); setRating(span, rating); }); $(ratingStars).unbind('click'); $(ratingStars).click(function () { var span = $(this).parent("span"); var newRating = $(this).attr("value"); var text = span.children("span"); var pID = span.attr("post"); var type = span.attr("sectiontype"); postData({ postID: pID, rating: newRating, sectionType: type }); span.attr("rating", newRating); setRating(span, newRating); }); function setRating(span, rating) { span.find(options.ratingStarsSpan).each(function () { var value = parseFloat($(this).attr("value")); var imgSrc = $(this).attr("class"); if (value <= rating) $(this).attr("class", imgSrc.replace("_off", "_on")); else $(this).attr("class", imgSrc.replace("_on", "_off")); }); } function postData(dataJsonArray) { $.ajax({ type: "POST", url: options.postInfoUrl, data: JSON.stringify(dataJsonArray), contentType: "application/json; charset=utf-8", dataType: "json", complete: function (xhr, status) { var data = xhr.responseText; if (xhr.status == 403) { window.location = options.loginUrl; } else if (status === 'error' || !data) { if (options.errorHandler) options.errorHandler(this); } else if (data == "nok") { if (options.onlyOneTimeHandler) options.onlyOneTimeHandler(this); } else { if (options.completeHandler) options.completeHandler(this); } } }); } }); }; })(jQuery); // ]]>
{ postID: pID, rating: newRating, sectionType: type }
<head> <title>@ViewBag.Title</title> <link href="@Url.Content("Content/starRating.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.StarRating.js")" type="text/javascript"></script> @RenderSection("JavaScript", required: false) </head>
@using System.Globalization @helper AddStarRating(int postId, double? average = 0, int? postRatingsCount = 0, string type = "BlogPost", string tooltip = "لطفا جهت رای دادن کلیک نمائید") { string actIt = "active "; if (!average.HasValue) { average = 0; } if (!postRatingsCount.HasValue) { postRatingsCount = 0; } <span class='postRating' rating='@average' post='@postId' title='@tooltip' sectiontype='@type'> @for (double i = .5; i <= 5.0; i = i + .5) { string left; if (i <= average) { left = (i * 2) % 2 == 1 ? "left_on" : "right_on"; } else { left = (i * 2) % 2 == 1 ? "left_off" : "right_off"; } <span class='rating stars @(actIt)star-@left' value='@i'></span> } @if (postRatingsCount > 0) { var ratingInfo = string.Format(CultureInfo.InvariantCulture, "امتیاز {0:0.00} از 5 توسط {1} نفر", average, postRatingsCount); <span>@ratingInfo</span> } else { <span></span> } </span> }
using System.Web.Mvc; using System.Web.UI; using jQueryMvcSample03.DataSource; using jQueryMvcSample03.Security; namespace jQueryMvcSample03.Controllers { public class HomeController : Controller { public ActionResult Index() { var postsList = BlogPostDataSource.GetLatestBlogPosts(pageNumber: 0); return View(postsList); //نمایش صفحه اصلی } [HttpPost] [AjaxOnly] [OutputCache(Location = OutputCacheLocation.None, NoStore = true)] public ActionResult SaveRatings(int? postId, double? rating, string sectionType) { if (postId == null || rating == null || string.IsNullOrWhiteSpace(sectionType)) return Content(null); //اعلام بروز خطا if (!this.HttpContext.CanUserVoteBasedOnCookies(postId.Value, sectionType)) return Content("nok"); //اعلام فقط یکبار مجاز هستید رای دهید switch (sectionType) //قسمتهای مختلف سایت که در جداول مختلفی قرار دارند نیز میتوانند گزینه امتیاز دهی داشته باشند { case "BlogPost": //الان شماره مطلب و رای ارسالی را داریم که میتوان نسبت به ذخیره آن اقدام کرد //مثلا //_blogPostsService.SaveRating(postId.Value, rating.Value); break; //... سایر قسمتهای دیگر سایت default: return Content(null); //اعلام بروز خطا } return Content("ok"); //اعلام موفقیت آمیز بودن ثبت اطلاعات } [HttpGet] public ActionResult Post(int? id) { if (id == null) return Redirect("/"); //todo: show the content here return Content("Post " + id.Value); } } }
using System; using System.Web; namespace jQueryMvcSample03.Security { public static class CookieHelper { public static bool CanUserVoteBasedOnCookies(this HttpContextBase httpContext, int postId, string sectionType) { string key = sectionType + "-" + postId; var value = httpContext.GetCookieValue(key); if (string.IsNullOrWhiteSpace(value)) { httpContext.AddCookie(key, key); return true; } return false; } public static void AddCookie(this HttpContextBase httpContextBase, string cookieName, string value) { httpContextBase.AddCookie(cookieName, value, DateTime.Now.AddDays(30)); } public static void AddCookie(this HttpContextBase httpContextBase, string cookieName, string value, DateTime expires) { var cookie = new HttpCookie(cookieName) { Expires = expires, Value = httpContextBase.Server.UrlEncode(value) // For Cookies and Unicode characters }; httpContextBase.Response.Cookies.Add(cookie); } public static string GetCookieValue(this HttpContextBase httpContext, string cookieName) { var cookie = httpContext.Request.Cookies[cookieName]; if (cookie == null) return string.Empty; //cookie doesn't exist // For Cookies and Unicode characters return httpContext.Server.UrlDecode(cookie.Value); } } }
using jQueryMvcSample03.Models; namespace jQueryMvcSample03.DataSource { public interface IBlogPostsService { void SaveRating(int postId, double rating); } public class SampleService : IBlogPostsService { /// <summary> /// یک نمونه از متد ذخیره سازی اطلاعات پیشنهادی /// فقط برای ایده گرفتن /// بدیهی است محل قرارگیری اصلی آن در لایه سرویس برنامه شما خواهد بود /// </summary> public void SaveRating(int postId, double rating) { BlogPost post = null; //post = _blogCtx.Find(postId); // بر اساس شماره مطلب، مطلب یافت شده و فیلدهای آن تنظیم میشوند if (post == null) return; if (!post.Rating.TotalRaters.HasValue) post.Rating.TotalRaters = 0; if (!post.Rating.TotalRating.HasValue) post.Rating.TotalRating = 0; if (!post.Rating.AverageRating.HasValue) post.Rating.AverageRating = 0; post.Rating.TotalRaters++; post.Rating.TotalRating += rating; post.Rating.AverageRating = post.Rating.TotalRating / post.Rating.TotalRaters; // todo: call save changes at the end. } } }
@using System.Text.RegularExpressions @model IList<jQueryMvcSample03.Models.BlogPost> <ul> @foreach (var item in Model) { <li> <fieldset> <legend>مطلب @item.Id</legend> <h5> @Html.ActionLink(linkText: item.Title, actionName: "Post", controllerName: "Home", routeValues: new { id = item.Id }, htmlAttributes: null) </h5> @item.Body <div class="post_rating"> @Html.Raw(Regex.Replace(@StarRatingHelper.AddStarRating(item.Id, item.Rating.AverageRating, item.Rating.TotalRaters, "BlogPost").ToHtmlString(), @">\s+<", "><")) </div> </fieldset> </li> } </ul>
@model IList<jQueryMvcSample03.Models.BlogPost> @{ ViewBag.Title = "Index"; var postInfoUrl = Url.Action(actionName: "SaveRatings", controllerName: "Home"); } <h2> سیستم امتیاز دهی</h2> @{ Html.RenderPartial("_ItemsList", Model); } @section JavaScript { <script type="text/javascript"> $(document).ready(function () { $(".rating.stars.active").StarRating({ ratingStarsSpan: '.rating.stars', postInfoUrl: '@postInfoUrl', loginUrl: '/login', errorHandler: function () { alert('خطایی رخ داده است'); }, completeHandler: function () { alert('با تشکر! رای شما با موفقیت ثبت شد'); }, onlyOneTimeHandler: function () { alert('فقط یکبار میتوانید به ازای هر مطلب رای دهید'); } }); }); </script> }
import React, { Component } from "react"; class Form extends Component { state = { data:{}, errors:{} }
import Joi from "@hapi/joi";
doSubmit = () => { // call the server console.log("Submitted!"); };
import Form from "./common/form"; // ... class LoginForm extends Form {
renderButton(label) { return ( <button disabled={this.validate()} className="btn btn-primary"> {label} </button> ); }
{this.renderButton("Login")}
import Input from "./input"; //... renderInput(name, label) { const { data, errors } = this.state; return ( <Input name={name} label={label} value={data[name]} onChange={this.handleChange} error={errors[name]} /> );
render() { return ( <form onSubmit={this.handleSubmit}> {this.renderInput("username", "Username")} {this.renderInput("password", "Password")} {this.renderButton("Login")} </form> ); }
renderInput(name, label, type = "text") { const { data, errors } = this.state; return ( <Input name={name} type={type} label={label} value={data[name]} onChange={this.handleChange} error={errors[name]} /> ); }
render() { return ( <form onSubmit={this.handleSubmit}> {this.renderInput("username", "Username")} {this.renderInput("password", "Password", "password")} {this.renderButton("Login")} </form> ); }
import React from "react"; const Input = ({ name, type, label, value, error, onChange }) => { return ( <div className="form-group"> <label htmlFor={name}>{label}</label> <input value={value} onChange={onChange} id={name} name={name} type={type} className="form-control" /> {error && <div className="alert alert-danger">{error}</div>} </div> ); }; export default Input;
<input value={value} name={name} type={type} onChange={onChange} id={name} className="form-control" />
import React from "react"; const Input = ({ name, label, error, ...rest }) => { return ( <div className="form-group"> <label htmlFor={name}>{label}</label> <input {...rest} name={name} id={name} className="form-control" /> {error && <div className="alert alert-danger">{error}</div>} </div> ); }; export default Input;
Vue.component('todo-item', { template: '\ <li>\ {{ title }}\ <button v-on:click="$emit(\'remove\')">Remove</button>\ </li>\ ' })
Vue.component('custom-input', { template: ` <input v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > ` })
<script type="x-template" id="my-template" src="template.js"> </script> <div id="app"></div> new Vue({ el: '#app', template: '#my-template' })
<div id="app"> <gallery inline-template> .... Vue.component('gallery', { ...
const template = ` <ul> <li v-for="item in items"> {{ item }} </li> </ul>`; const compiledTemplate = Vue.compile(template); new Vue({ el: '#app', data() { return { items: ['Item1', 'Item2'] } }, render(createElement) { return compiledTemplate.render.call(this, createElement); } });
new Vue({ el: '#app', data: { msg: 'Show the message' }, methods: { hello () { alert('Here is the message') } }, render (createElement) { return createElement( 'span', { class: { 'my-class': true }, on: { click: this.hello } }, [ this.msg ] ); }, });
new Vue({ el: '#app', data: { msg: 'Show the message.' }, methods: { hello () { alert('This is the message.') } }, render(h) { return ( <span class={{ 'my-class': true }} on-click={ this.hello } > { this.msg } </span> ) } });
var app = angular.module('myApp', []); app.factory('BookData', function () { var books = [ { code: 1, name: 'book1', }, { code: 2, name: 'book2', }, { code: 3, name: 'book3', }, { code: 4, name: 'book4', }, { code: 5, name: 'book5', } ]; return books; });
app.controller('controller1', function ($scope, BookData) { $scope.books = BookData; });
app.controller('controller2', function ($scope, BookData) { $scope.books = BookData; });
<script type="text/javascript" src="~/scripts/app/controller1.js"></script>
<div ng-app="myApp"> <div ng-controller="controller1"> <p>Data from controller1</p> <table> <tr ng-repeat="book in books"> <td> {{book.code}} </td> <td> {{book.name}} </td> </tr> </table> </div> <div ng-controller="controller2"> <p>Data from controller2</p> <table> <tr ng-repeat="book in books"> <td> {{book.code}} </td> <td> {{book.name}} </td> </tr> </table> </div> </div>
حال با استفاده از ابزار ASP.NET Configuration دو کاربر جدید بسازید: oldAdminUser و oldUser.
نقش جدیدی با نام Admin بسازید و کاربر oldAdminUser را به آن اضافه کنید.
بخش جدیدی با نام Admin در سایت خود بسازید و فرمی بنام Default.aspx به آن اضافه کنید. همچنین فایل web.config این قسمت را طوری پیکربندی کنید تا تنها کاربرانی که در نقش Admin هستند به آن دسترسی داشته باشند. برای اطلاعات بیشتر به این لینک مراجعه کنید.
پنجره Server Explorer را باز کنید و جداول ساخته شده توسط SQL Membership را بررسی کنید. اطلاعات اصلی کاربران که برای ورود به سایت استفاده میشوند، در جداول aspnet_Users و aspnet_Membership ذخیره میشوند. دادههای مربوط به نقشها نیز در جدول aspnet_Roles ذخیره خواهند شد. رابطه بین کاربران و نقشها نیز در جدول aspnet_UsersInRoles ذخیره میشود، یعنی اینکه هر کاربری به چه نقش هایی تعلق دارد.
برای مدیریت اساسی سیستم عضویت، مهاجرت جداول ذکر شده به سیستم جدید ASP.NET Identity کفایت میکند.
پروژه را Build کنید تا مطمئن شوید هیچ خطایی وجود ندارد.
در پنجره کوئری باز شده، تمام محتویات فایل Migrations.sql را کپی کنید. سپس اسکریپت را با کلیک کردن دکمه Execute اجرا کنید.
ممکن است با اخطاری مواجه شوید مبنی بر آنکه امکان حذف (drop) بعضی از جداول وجود نداشت. دلیلش آن است که چهار عبارت اولیه در این اسکریپت، تمام جداول مربوط به Identity را در صورت وجود حذف میکنند. از آنجا که با اجرای اولیه این اسکریپت چنین جداولی وجود ندارند، میتوانیم این خطاها را نادیده بگیریم. حال پنجره Server Explorer را تازه (refresh) کنید و خواهید دید که پنج جدول جدید ساخته شده اند.
لیست زیر نحوه Map کردن اطلاعات از جداول SQL Membership به سیستم Identity را نشان میدهد.
کلاس User باید کلاس IdentityUser را که در اسمبلی Microsoft.AspNet.Identity.EntityFramework وجود دارد گسترش دهد. خاصیت هایی را تعریف کنید که نماینده الگوی جدول AspNetUser هستند. خواص ID, Username, PasswordHash و SecurityStamp در کلاس IdentityUser تعریف شده اند، بنابراین این خواص را در لیست زیر نمیبینید.
public class User : IdentityUser { public User() { CreateDate = DateTime.Now; IsApproved = false; LastLoginDate = DateTime.Now; LastActivityDate = DateTime.Now; LastPasswordChangedDate = DateTime.Now; LastLockoutDate = DateTime.Parse("1/1/1754"); FailedPasswordAnswerAttemptWindowStart = DateTime.Parse("1/1/1754"); FailedPasswordAttemptWindowStart = DateTime.Parse("1/1/1754"); } public System.Guid ApplicationId { get; set; } public string MobileAlias { get; set; } public bool IsAnonymous { get; set; } public System.DateTime LastActivityDate { get; set; } public string MobilePIN { get; set; } public string Email { get; set; } public string LoweredEmail { get; set; } public string LoweredUserName { get; set; } public string PasswordQuestion { get; set; } public string PasswordAnswer { get; set; } public bool IsApproved { get; set; } public bool IsLockedOut { get; set; } public System.DateTime CreateDate { get; set; } public System.DateTime LastLoginDate { get; set; } public System.DateTime LastPasswordChangedDate { get; set; } public System.DateTime LastLockoutDate { get; set; } public int FailedPasswordAttemptCount { get; set; } public System.DateTime FailedPasswordAttemptWindowStart { get; set; } public int FailedPasswordAnswerAttemptCount { get; set; } public System.DateTime FailedPasswordAnswerAttemptWindowStart { get; set; } public string Comment { get; set; } }
حال برای دسترسی به دیتابیس مورد نظر، نیاز به یک DbContext داریم. اسمبلی Microsoft.AspNet.Identity.EntityFramework کلاسی با نام IdentityDbContext دارد که پیاده سازی پیش فرض برای دسترسی به دیتابیس ASP.NET Identity است. نکته قابل توجه این است که IdentityDbContext آبجکتی از نوع TUser را میپذیرد. TUser میتواند هر کلاسی باشد که از IdentityUser ارث بری کرده و آن را گسترش میدهد.
در پوشه Models کلاس جدیدی با نام ApplicationDbContext بسازید که از IdentityDbContext ارث بری کرده و از کلاس User استفاده میکند.
public class ApplicationDbContext : IdentityDbContext<User> { }
مدیریت کاربران در ASP.NET Identity توسط کلاسی با نام UserManager انجام میشود که در اسمبلی Microsoft.AspNet.Identity.EntityFramework قرار دارد. چیزی که ما در این مرحله نیاز داریم، کلاسی است که از UserManager ارث بری میکند و آن را طوری توسعه میدهد که از کلاس User استفاده کند.
در پوشه Models کلاس جدیدی با نام UserManager بسازید.
public class UserManager : UserManager<User> { }
کلمه عبور کاربران بصورت رمز نگاری شده در دیتابیس ذخیره میشوند. الگوریتم رمز نگاری SQL Membership با سیستم ASP.NET Identity تفاوت دارد. هنگامی که کاربران قدیمی به سایت وارد میشوند، کلمه عبورشان را توسط الگوریتمهای قدیمی SQL Membership رمزگشایی میکنیم، اما کاربران جدید از الگوریتمهای ASP.NET Identity استفاده خواهند کرد.
کلاس UserManager خاصیتی با نام PasswordHasher دارد. این خاصیت نمونه ای از یک کلاس را ذخیره میکند، که اینترفیس IPasswordHasher را پیاده سازی کرده است. این کلاس هنگام تراکنشهای احراز هویت کاربران استفاده میشود تا کلمههای عبور را رمزنگاری/رمزگشایی شوند. در کلاس UserManager کلاس جدیدی بنام SQLPasswordHasher بسازید. کد کامل را در لیست زیر مشاهده میکنید.
public class SQLPasswordHasher : PasswordHasher { public override string HashPassword(string password) { return base.HashPassword(password); } public override PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword) { string[] passwordProperties = hashedPassword.Split('|'); if (passwordProperties.Length != 3) { return base.VerifyHashedPassword(hashedPassword, providedPassword); } else { string passwordHash = passwordProperties[0]; int passwordformat = 1; string salt = passwordProperties[2]; if (String.Equals(EncryptPassword(providedPassword, passwordformat, salt), passwordHash, StringComparison.CurrentCultureIgnoreCase)) { return PasswordVerificationResult.SuccessRehashNeeded; } else { return PasswordVerificationResult.Failed; } } } //This is copied from the existing SQL providers and is provided only for back-compat. private string EncryptPassword(string pass, int passwordFormat, string salt) { if (passwordFormat == 0) // MembershipPasswordFormat.Clear return pass; byte[] bIn = Encoding.Unicode.GetBytes(pass); byte[] bSalt = Convert.FromBase64String(salt); byte[] bRet = null; if (passwordFormat == 1) { // MembershipPasswordFormat.Hashed HashAlgorithm hm = HashAlgorithm.Create("SHA1"); if (hm is KeyedHashAlgorithm) { KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm; if (kha.Key.Length == bSalt.Length) { kha.Key = bSalt; } else if (kha.Key.Length < bSalt.Length) { byte[] bKey = new byte[kha.Key.Length]; Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length); kha.Key = bKey; } else { byte[] bKey = new byte[kha.Key.Length]; for (int iter = 0; iter < bKey.Length; ) { int len = Math.Min(bSalt.Length, bKey.Length - iter); Buffer.BlockCopy(bSalt, 0, bKey, iter, len); iter += len; } kha.Key = bKey; } bRet = kha.ComputeHash(bIn); } else { byte[] bAll = new byte[bSalt.Length + bIn.Length]; Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length); Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length); bRet = hm.ComputeHash(bAll); } } return Convert.ToBase64String(bRet); } }
دقت کنید تا فضاهای نام System.Text و System.Security.Cryptography را وارد کرده باشید.
متد EncodePassword کلمه عبور را بر اساس پیاده سازی پیش فرض SQL Membership رمزنگاری میکند. این الگوریتم از System.Web گرفته میشود. اگر اپلیکیشن قدیمی شما از الگوریتم خاصی استفاده میکرده است، همینجا باید آن را منعکس کنید. دو متد دیگر نیز بنامهای HashPassword و VerifyHashedPassword نیاز داریم. این متدها از EncodePassword برای رمزنگاری کلمههای عبور و تایید آنها در دیتابیس استفاده میکنند.
سیستم SQL Membership برای رمزنگاری (Hash) کلمههای عبور هنگام ثبت نام و تغییر آنها توسط کاربران، از PasswordHash, PasswordSalt و PasswordFormat استفاده میکرد. در روند مهاجرت، این سه فیلد در ستون PasswordHash جدول AspNetUsers ذخیره شده و با کاراکتر '|' جدا شده اند. هنگام ورود کاربری به سایت، اگر کله عبور شامل این فیلدها باشد از الگوریتم SQL Membership برای بررسی آن استفاده میکنیم. در غیر اینصورت از پیاده سازی پیش فرض ASP.NET Identity استفاده خواهد شد. با این روش، کاربران قدیمی لازم نیست کلمههای عبور خود را صرفا بدلیل مهاجرت اپلیکیشن ما تغییر دهند.
کلاس UserManager را مانند قطعه کد زیر بروز رسانی کنید.
public UserManager() : base(new UserStore<User>(new ApplicationDbContext())) { this.PasswordHasher = new SQLPasswordHasher(); }
private Guid GetApplicationID() { using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString)) { string queryString = "SELECT ApplicationId from aspnet_Applications WHERE ApplicationName = '/'"; //Set application name as in database SqlCommand command = new SqlCommand(queryString, connection); command.Connection.Open(); var reader = command.ExecuteReader(); while (reader.Read()) { return reader.GetGuid(0); } return Guid.NewGuid(); } }
var currentApplicationId = GetApplicationID(); User user = new User() { UserName = Username.Text, ApplicationId=currentApplicationId, …};
import { Component } from '@angular/core'; import '../../public/css/styles.css'; @Component({ selector: 'my-app', template: require('./app.component.html'), styles: [require('./app.component.css')] }) export class AppComponent { }