NET 8 Preview 2. منتشر شد
Roslyn #6
پیشنیاز این بحث نصب مواردی است که در مطلب «شروع به کار با Roslyn » در قسمت دوم عنوان شدند:
الف) نصب SDK ویژوال استودیوی 2015
ب) نصب قالبهای ایجاد پروژههای مخصوص Roslyn
البته این قالبها چیزی بیشتر از ایجاد یک پروژهی کلاس Library جدید و افزودن ارجاعاتی به بستهی نیوگت Microsoft.CodeAnalysis، نیستند. اما درکل زمان ایجاد و تنظیم این نوع پروژهها را خیلی کاهش میدهند و همچنین یک پروژهی تست را ایجاد کرده و تولید بستهی نیوگت و فایل VSIX را نیز بسیار ساده میکنند.
هدف از تولید Analyzers
بسیاری از مجموعهها و شرکتها، یک سری قوانین و اصول خاصی را برای کدنویسی وضع میکنند تا به کدهایی با قابلیت خوانایی بهتر و نگهداری بیشتر برسند. با استفاده از Roslyn و آنالیز کنندههای آن میتوان این قوانین را پیاده سازی کرد و خطاها و اخطارهایی را به برنامه نویسها جهت رفع اشکالات موجود، نمایش داده و گوشزد کرد. بنابراین هدف از آنالیز کنندههای Roslyn، سهولت تولید ابزارهایی است که بتوانند برنامه نویسها را ملزم به رعایت استانداردهای کدنویسی کنند.
همچنین معلمها نیز میتوانند از این امکانات جهت ارائهی نکات ویژهای به تازهکاران کمک بگیرند. برای مثال اگر این قسمت از کد اینگونه باشد، بهتر است؛ مثلا بهتر است فیلدهای سطح کلاس، خصوصی تعریف شوند و امکان دسترسی به آنها صرفا از طریق متدهایی که قرار است با آنها کار کنند صورت گیرد.
این آنالیز کنندها به صورت پویا در حین تایپ کدها در ویژوال استودیو فعال میشوند و یا حتی به صورت خودکار در طی پروسهی Build پروژه نیز میتوانند ظاهر شده و خطاها و اخطارهایی را گزارش کنند.
بررسی مثال معتبری که میتواند بهتر باشد
در اینجا یک کلاس نمونه را مشاهده میکنید که در آن فیلدهای کلاس به صورت public تعریف شدهاند.
public class Student { public string FirstName; public string LastName; public int TotalPointsEarned; public void TakeExam(int pointsForExam) { TotalPointsEarned += pointsForExam; } public void ExtraCredit(int extraPoints) { TotalPointsEarned += extraPoints; } public int PointsEarned { get { return TotalPointsEarned; } } }
بنابراین در ادامه هدف ما این است که یک Roslyn Analyzer جدید را طراحی کنیم تا از طریق آن هشدارهایی را جهت تبدیل فیلدهای عمومی به خصوصی، به برنامه نویس نمایش دهیم.
با اجرای افزونهی View->Other windows->Syntax visualizer، تصویر فوق نمایان خواهد شد. بنابراین در اینجا نیاز است FieldDeclarationها را یافته و سپس tokenهای آنها را بررسی کنیم و مشخص کنیم که آیا نوع یا Kind آنها public است (PublicKeyword) یا خیر؟ اگر بلی، آن مورد را به صورت یک Diagnostic جدید گزارش میدهیم.
ایجاد اولین Roslyn Analyzer
پس از نصب پیشنیازهای بحث، به شاخهی قالبهای extensibility در ویژوال استودیو مراجعه کرده و یک پروژهی جدید از نوع Analyzer with code fix را آغاز کنید.
قالب Stand-alone code analysis tool آن دقیقا همان برنامههای کنسول بحث شدهی در قسمتهای قبل است که تنها ارجاعی را به بستهی نیوگت Microsoft.CodeAnalysis به صورت خودکار دارد.
قالب پروژهی Analyzer with code fix علاوه بر ایجاد پروژههای Test و VSIX جهت بسته بندی آنالایزر تولید شده، دارای دو فایل DiagnosticAnalyzer.cs و CodeFixProvider.cs پیش فرض نیز هست. این دو فایل قالبهایی را جهت شروع به کار تهیهی آنالیز کنندههای مبتنی بر Roslyn ارائه میدهند. کار DiagnosticAnalyzer آنالیز کد و ارائهی خطاهایی جهت نمایش به ویژوال استودیو است و CodeFixProvider این امکان را مهیا میکند که این خطای جدید عنوان شدهی توسط آنالایزر، چگونه باید برطرف شود و راهکار بازنویسی Syntax tree آنرا ارائه میدهد.
همین پروژهی پیش فرض ایجاد شده نیز قابل اجرا است. اگر بر روی F5 کلیک کنید، یک کپی جدید و محصور شدهی ویژوال استودیو را باز میکند که در آن افزونهی در حال تولید به صورت پیش فرض و محدود نصب شدهاست. اکنون اگر پروژهی جدیدی را جهت آزمایش، در این وهلهی محصور شدهی ویژوال استودیو باز کنیم، قابلیت اجرای خودکار آنالایزر در حال توسعه را فراهم میکند. به این ترتیب کار تست و دیباگ آنالایزرها با سهولت بیشتری قابل انجام است.
این پروژهی پیش فرض، کار تبدیل نام فضاهای نام را به upper case، به صورت خودکار انجام میدهد (که البته بیمعنا است و صرفا جهت نمایش و ارائهی قالبهای شروع به کار مفید است).
نکتهی دیگر آن، تعریف تمام رشتههای مورد نیاز آنالایزر در یک فایل resource به نام Resources.resx است که در جهت بومی سازی پیامهای خطای آن میتواند بسیار مفید باشد.
در ادامه کدهای فایل DiagnosticAnalyzer.cs را به صورت ذیل تغییر دهید:
using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace CodingStandards { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class CodingStandardsAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = "CodingStandards"; // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. internal static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); internal static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); internal const string Category = "Naming"; internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } public override void Initialize(AnalysisContext context) { // TODO: Consider registering other actions that act on syntax instead of or in addition to symbols context.RegisterSyntaxNodeAction(analyzeFieldDeclaration, SyntaxKind.FieldDeclaration); } static void analyzeFieldDeclaration(SyntaxNodeAnalysisContext context) { var fieldDeclaration = context.Node as FieldDeclarationSyntax; if (fieldDeclaration == null) return; var accessToken = fieldDeclaration .ChildTokens() .SingleOrDefault(token => token.Kind() == SyntaxKind.PublicKeyword); // Note: Not finding protected or internal if (accessToken.Kind() != SyntaxKind.None) { // Find the name of the field: var name = fieldDeclaration.DescendantTokens() .SingleOrDefault(token => token.IsKind(SyntaxKind.IdentifierToken)).Value; var diagnostic = Diagnostic.Create(Rule, fieldDeclaration.GetLocation(), name, accessToken.Value); context.ReportDiagnostic(diagnostic); } } } }
اولین کاری که در این کلاس انجام شده، خواندن سه رشتهی AnalyzerDescription (توضیحی در مورد آنالایزر)، AnalyzerMessageFormat (پیامی که به کاربر نمایش داده میشود) و AnalyzerTitle (عنوان پیام) از فایل Resources.resx است. این فایل را گشوده و محتوای آنرا مطابق تنظیمات ذیل تغییر دهید:
سپس کار به متد Initialize میرسد. در اینجا برخلاف مثالهای قسمتهای قبل، context مورد نیاز، توسط پارامترهای override شدهی کلاس پایه DiagnosticAnalyzer فراهم میشوند. برای مثال در متد Initialize، این فرصت را خواهیم داشت تا به ویژوال استودیو اعلام کنیم، قصد آنالیز فیلدها یا FieldDeclaration را داریم. پارامتر اول متد RegisterSyntaxNodeAction یک delegate یا Action است. این Action کار فراهم آوردن context کاری را برعهده دارد که نحوهی استفادهی از آنرا در متد analyzeFieldDeclaration میتوانید ملاحظه کنید.
سپس در اینجا نوع نود در حال آنالیز (همان نودی که کاربر در ویژوال استودیو انتخاب کردهاست یا در حال کار با آن است)، به نوع تعریف فیلد تبدیل میشود. سپس توکنهای آن استخراج شده و بررسی میشود که آیا یکی از این توکنها کلمهی کلیدی public هست یا خیر؟ اگر این فیلد عمومی تعریف شده بود، نام آنرا یافته و به عنوان یک Diagnostic جدید بازگشت و گزارش میدهیم.
ایجاد اولین Code fixer
در ادامه فایل CodeFixProvider.cs پیش فرض را گشوده و تغییرات ذیل را به آن اعمال کنید. در اینجا مهمترین تغییر صورت گرفته نسبت به قالب پیش فرض، اضافه شدن متد makePrivateDeclarationAsync بجای متد MakeUppercaseAsync از پیش موجود آن است:
using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace CodingStandards { [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CodingStandardsCodeFixProvider)), Shared] public class CodingStandardsCodeFixProvider : CodeFixProvider { public sealed override ImmutableArray<string> FixableDiagnosticIds { get { return ImmutableArray.Create(CodingStandardsAnalyzer.DiagnosticId); } } public sealed override FixAllProvider GetFixAllProvider() { return WellKnownFixAllProviders.BatchFixer; } public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; // Find the type declaration identified by the diagnostic. var declaration = root.FindToken(diagnosticSpan.Start) .Parent.AncestorsAndSelf().OfType<FieldDeclarationSyntax>() .First(); // Register a code action that will invoke the fix. context.RegisterCodeFix( CodeAction.Create("Make Private", c => makePrivateDeclarationAsync(context.Document, declaration, c)), diagnostic); } async Task<Document> makePrivateDeclarationAsync(Document document, FieldDeclarationSyntax declaration, CancellationToken c) { var accessToken = declaration.ChildTokens() .SingleOrDefault(token => token.Kind() == SyntaxKind.PublicKeyword); var privateAccessToken = SyntaxFactory.Token(SyntaxKind.PrivateKeyword); var root = await document.GetSyntaxRootAsync(c); var newRoot = root.ReplaceToken(accessToken, privateAccessToken); return document.WithSyntaxRoot(newRoot); } } }
کاری که در متد RegisterCodeFixesAsync انجام میشود، مشخص کردن اولین مکانی است که مشکلی در آن گزارش شدهاست. سپس به این مکان منوی Make Private با متد متناظر با آن معرفی میشود. در این متد، اولین توکن public، مشخص شده و سپس با یک توکن private جایگزین میشود. اکنون این syntax tree بازنویسی شده بازگشت داده میشود. با Syntax Factory در قسمت سوم آشنا شدیم.
خوب، تا اینجا یک analyzer و یک code fixer را تهیه کردهایم. برای آزمایش آن دکمهی F5 را فشار دهید تا وهلهای جدید از ویژوال استودیو که این آنالایزر جدید در آن نصب شدهاست، آغاز شود. البته باید دقت داشت که در اینجا باید پروژهی CodingStandards.Vsix را به عنوان پروژهی آغازین ویژوال استودیو معرفی کنید؛ چون پروژهی class library آنالایزرها را نمیتوان مستقیما اجرا کرد. همچنین یکبار کل solution را نیز build کنید.
پس از اینکه وهلهی جدید ویژوال استودیو شروع به کار کرد (بار اول اجرای آن کمی زمانبر است؛ زیرا باید تنظیمات وهلهی ویژهی اجرای افزونهها را از ابتدا اعمال کند)، همان پروژهی Student ابتدای بحث را در آن باز کنید.
نتیجهی اعمال این افزونهی جدید را در تصویر فوق ملاحظه میکنید. زیر سطرهای دارای فیلد عمومی، خط قرمز کشیده شدهاست (به علت تعریف DiagnosticSeverity.Error). همچنین حالت فعلی و حالت برطرف شده را نیز با رنگهای قرمز و سبز میتوان مشاهده کرد. کلیک بر روی گزینهی make private، سبب اصلاح خودکار آن سطر میگردد.
روش دوم آزمایش یک Roslyn Analyzer
همانطور که از انتهای بحث قسمت دوم بهخاطر دارید، این آنالایزرها را میتوان به کامپایلر نیز معرفی کرد. روش انجام اینکار در ویژوال استودیوی 2015 در تصویر ذیل نمایش داده شدهاست.
نود references را باز کرده و سپس بر روی گزینهی analyzers کلیک راست نمائید. در اینجا گزینهی Add analyzer را انتخاب کنید. در صفحهی باز شده بر روی دکمهی browse کلیک کنید. در اینجا میتوان فایل اسمبلی موجود در پوشهی CodingStandards\bin\Debug را به آن معرفی کرد.
بلافاصله پس از معرفی این اسمبلی، آنالایزر آن شناسایی شده و همچنین فعال میگردد.
در این حالت اگر برنامه را کامپایل کنیم، با خطاهای جدید فوق متوقف خواهیم شد و برنامه کامپایل نمیشود (به علت تعریف DiagnosticSeverity.Error).
مشکل در IDENTITY و سیستم کاربران
- قسمت پروژهها فقط مرتبط هست به مشکلات پروژهها و هیچ هدف دیگری ندارد. لطفا رعایت کنید.
عدم رعایت این مساله در آینده، سبب حذف شما از سایت خواهد شد.
سایت ما هدف تبدیل شدن به انجمن عمومی پرسش و پاسخ را ندارد. از روز اول نداشتهاست.
- این پروژه از روش دات نت 4 استفاده میکند. به عبارتی از ASP.NET Identity نوشته شده برای دات نت 4.5 به بعد کمک نگرفتهاست و از روش Forms authentication استفاده میکند. اطلاعات بیشتر
- برای استفاده از کلاسهای شخصی در ASP.NET Identity به این مقاله مراجعه کنید.
Claim type: http://schemas.microsoft.com/claims/authnmethodsreferences - Claim value: pwd Claim type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier - Claim value: 1 Claim type: auth_time - Claim value: 1651417673 Claim type: http://schemas.microsoft.com/identity/claims/identityprovider - Claim value: local Claim type: name - Claim value: meysanm Claim type: given_name - Claim value: soleymani Claim type: role - Claim value: admin Claim type: http://schemas.microsoft.com/claims/authnmethodsreferences - Claim value: pwd Claim type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier - Claim value: 1 Claim type: auth_time - Claim value: 1651417673 Claim type: http://schemas.microsoft.com/identity/claims/identityprovider - Claim value: local Claim type: name - Claim value: meysanm Claim type: given_name - Claim value: soleymani Claim type: role - Claim value: admin
مروری بر کاربردهای Action و Func - قسمت اول
مراحل نحوه اجرای برنامه:
نصب کتابخانههای زیر:
//client Install-Package angularjs Install-Package angular-strap Install-Package Microsoft.AspNet.SignalR.JS install-package AngularJs.SignalR.Hub Install-Package jQuery.TimeAgo Install-Package FontAwesome Install-Package toastr Install-Package Twitter.Bootstrap.RTL bower install angular-smilies //server Install-Package Newtonsoft.Json Install-Package Microsoft.AspNet.SignalR Install-Package EntityFramework
گامهای برنامه:
public partial class Message { public int Id { get; set; } public string Sender { get; set; } public string Receiver { get; set; } public string Body { get; set; } public DateTimeOffset? CreationTime { get; set; } public int? SessionId { get; set; } public virtual Session Session { get; set; } } public partial class Session { public Session() { Messages = new List<Message>(); Sessions = new List<Session>(); } public int Id { get; set; } public string AgentName { get; set; } public string CustomerName { get; set; } public DateTime CreatedDateTime { get; set; } public int? ParentId { get; set; } public virtual Session Parent { get; set; } public virtual ICollection<Message> Messages { get; set; } public virtual ICollection<Session> Sessions { get; set; } }
2- ایجاد ویو مدلهای زیر
public class UserInformation { public string ConnectionId { get; set; } public bool IsOnline { get; set; } public string UserName { get; set; } } public class ChatSessionVm { public string Key { get; set; } public List<string> Value { get; set; } } public class AgentViewModel { public int Id { get; set; } public string CustomerName { get; set; } public int Lenght { get; set; } public DateTimeOffset? Date { get; set; } }
3- ایجاد Hub در سرور
[HubName("chatHub")] public class ChatHub : Microsoft.AspNet.SignalR.Hub { }
listeners متدهای سمت کلاینت
$scope.myHub = new hub("chatHub", { listeners: {}, methods: [] })
$scope.myHub.promise.done(function () { $scope.myHub.init(); $scope.myHub.promise.done(function () { }); });
public void Init() { _chatSessions = _chatSessions ?? (_chatSessions = new List<ChatSessionVm>()); _agents = _agents ?? (_agents = new ConcurrentDictionary<string, UserInformation>()); Clients.Caller.onlineStatus(_agents.Count(x => x.Value.IsOnline) > 0); }
5-وضعیت کارشناسان :
$scope.requestChat = function (msg) { if (!defaultCustomerUserName) { //گرفتن کاربر لاگین شده //ما از آرایه تصادفی استفاده میکنیم var nameDefaultArray = [ 'حسین', 'حسن', 'علی', 'عباس', 'زهرا', 'سمیه' ]; defaultCustomerUserName=nameDefaultArray[Math.floor(Math.random() * nameDefaultArray.length)]; } var userName = defaultCustomerUserName; if (!$scope.chatId) { $scope.chatId = sessionStorage.getItem(chatKey); $http.get("http://ipinfo.io") .success(function (response) { $scope.myHub.logVisit(response.city, response.country, msg, userName); }).error(function (e, status, headers, config) { $scope.myHub.logVisit("Tehran", "Ir", msg, userName) }); $scope.myHub.requestChat(msg); $scope.chatTitle = $scope.options.waitingForOperator; $scope.pendingRequestChat = true; } else { $scope.myHub.clientSendMessage(msg, userName); }; $scope.message = ""; };
6-مشاهده تقاضای مکالمه کاربران توسط کارشناسان:
public void AcceptRequestChat(string customerConnectionId, string body, string userName) { var agent = FindAgent(Context.ConnectionId); var session = _chatSessions.FirstOrDefault(item => item.Key.Equals(agent.Key)); if (session == null) { _chatSessions.Add(new ChatSessionVm { Key = agent.Key, Value = new List<string> { customerConnectionId } }); } else { session.Value.Add(customerConnectionId); } Clients.Client(Context.ConnectionId).agentChat(customerConnectionId, body, userName); Clients.Client(customerConnectionId).clientChat(customerConnectionId, agent.Value.UserName); foreach (var item in _agents.Where(item => item.Value.IsOnline)) { Clients.Client(item.Value.ConnectionId).refreshChatWith(agent.Value.UserName, customerConnectionId); } var session = _db.Sessions.Add(new Session { AgentName = agent.Key, CustomerName = userName, CreatedDateTime = DateTime.Now }); _db.SaveChanges(); var message = new Message { CreationTime = DateTime.Now, Sender = agent.Key, Receiver = userName, body=body, Session = session }; _db.Messages.Add(message); _db.SaveChanges(); }
public void CloseChat(string id) { var findAgent = FindAgent(Context.ConnectionId); var session = _chatSessions.FirstOrDefault(item => item.Value.Contains(id)); if (session == null) return; Clients.Client(id).clientAddMessage(findAgent.Key, "مکالمه شما با کارشناس مربوطه به اتمام رسیده است"); foreach (var agent in _agents) { Clients.Client(agent.Value.ConnectionId).refreshLeaveChat(agent.Value.UserName, id); } _chatSessions.Remove(session); }
8-انتقال مکالمه مشتری به کارشناسی دیگر
public void EngageVisitor(string newAgentId, string cumtomerId, string customerName,string clientSessionId) { #region remove session of current agent var currentAgent = FindAgent(Context.ConnectionId); var currentSession = _chatSessions.FirstOrDefault(item => item.Value.Contains(cumtomerId)); if (currentSession != null) { _chatSessions.Remove(currentSession); } #endregion #region add session to new agent var newAgent = FindAgent(newAgentId); var newSession = _chatSessions.FirstOrDefault(item => item.Key.Equals(newAgent.Key)); if (newSession == null) { _chatSessions.Add(new ChatSessionVm { Key = newAgent.Key, Value = new List<string> { cumtomerId } }); } else { newSession.Value.Add(cumtomerId); } #endregion Clients.Client(currentAgent.Value.ConnectionId).addMessage(cumtomerId, newAgent.Key, "ادامه مکالمه به کارشناس " + newAgent.Key + "مقابل منتقل شد"); Clients.Client(newAgentId).addMessage(cumtomerId, currentAgent.Key, "لطفا مکالمه را ادامه دهید.با تشکر"); Clients.Client(cumtomerId).clientAddMessage(newAgent.Value.UserName, "مکالمه شما با کارشناس زیر برقرار گردید" + newAgent.Key); var session = _db.Sessions.FirstOrDefault (item => item.AgentName.Equals(currentAgent.Value.UserName) && item.CustomerName.Equals(customerName)); if (session != null) { var sessionId = session.Id; var messages = _db.Messages.Where(item => item.Session.Id.Equals(sessionId)); var result = JsonConvert.SerializeObject(messages, new Formatting(), _settings); Clients.Client(newAgentId).visitorSwitchConversation (Context.ConnectionId, customerName, result, clientSessionId); } foreach (var item in _agents.Where(item => item.Value.IsOnline)) { Clients.Client(item.Value.ConnectionId).refreshChatWith(newAgent.Value.UserName, cumtomerId); } _db.Sessions.Add(new Session { AgentName = newAgent.Key, CustomerName = customerName, CreatedDateTime = DateTime.Now, Parent = _db.Sessions.Where(item => item.AgentName.Equals(currentAgent.Key) && item.CustomerName.Equals(customerName)).OrderByDescending(item => item.Id).FirstOrDefault() }); _db.SaveChanges(); }
var app = angular.module("app", ["SignalR", 'ngRoute', 'ngAnimate', 'ngSanitize', 'mgcrea.ngStrap', 'angular-smilies']); app.config(["$routeProvider", "$provide", "$httpProvider", "$locationProvider", function ($routeProvider, $provide, $httpProvider, $locationProvider) { $routeProvider. when('/', { templateUrl: 'app/views/home.html', controller: "HomeCtrl" }). when('/agent', { templateUrl: 'app/views/agent.html', controller: "ChatCtrl" }) .otherwise({ redirectTo: "/" });; }]); app.controller("HomeCtrl", ["$scope", function ($scope) { $scope.title = "home"; }]) app.controller("ChatCtrl", ["$scope", "Hub", "$location", "$http", "$rootScope", function ($scope, hub, $location, $http, $rootScope) { if (!$scope.myHub) { var chatKey = "angular-signalr"; var defaultCustomerUserName = null; function getid(id) { var find = false; var position = null; angular.forEach($scope.chatConversation, function (index, i) { if (index.id === id && !find) { find = true; position = i; return; } }); return position; } function apply() { $scope.$apply(); } $scope.boxheader = function () { var height = 0; $("#chat-box").slideToggle('slow', function () { if ($("#chat-box-header").css("bottom") === "0px") { height = $("#chat-box").height() + 20; } else { height = 0; } $("#chat-box-header").css("bottom", height); }); }; var init = function () { $scope.agent = { id: "", name: "", isOnline: false }; $rootScope.msg = ""; $scope.alarmStatus = false; $scope.options = { offlineTitle: "آفلاین", onlineTitle: "آنلاین", waitingForOperator: "لطفا منتظر بمانید تا به اپراتور وصل شوید", emailSent: "ایمیل ارسال گردید", emailFailed: "متاسفانه ایمیل ارسال نگردید", logOut: "خروج", setting: "تنظیمات", conversion: "آرشیو", edit: "ویرایش", alarm: "قطع/وصل کردن صدا", complete: "تکمیل", pending: "منتظر ماندن", reject: "عدم پذیرش", lock: "آنلاین شدن", unlock: "آفلاین شدن", alarmOn: "روشن", alarmOff: "خاموش", upload: "آپلود" }; $scope.chatConversation = []; $scope.chatSessions = []; $scope.customerVisit = []; $scope.agentClientMsgs = []; $scope.clientAgentMsg = []; }(); //تعریف هاب به همراه متدهای آن $scope.myHub = new hub("chatHub", { listeners: { "clientChat": function (id, agentName) { $scope.clientAgentMsg.push({ name: agentName, msg: "با سلام در خدمت میباشم" }); $scope.chatTitle = "کارشناس: " + agentName; $scope.pendingRequestChat = false; sessionStorage.setItem(chatKey, id); }, "agentChat": function (id, firstComment, customerName) { var date = new Date(); var position = getid(id); if (position > 0) { $scope.chatSessions[position].length = $scope.chatConversation[position].length + 1; $scope.chatSessions[position].date = date.toISOString(); return; } else { $scope.chatConversation.push({ id: id, sessions: [{ name: customerName, msg: firstComment, date: date }], agentName: $scope.agent.name, customerName: customerName, dateStartChat: date.getHours() + ":" + date.getMinutes(), }); $scope.chatSessions.push({ id: id, length: 1, userName: customerName, date: date.toISOString() }); } sessionStorage.setItem(chatKey, id); apply(); }, //برروز رسانی لیست برای کارشناسان "refreshChatWith": function (agentName, customerConnectionId) { angular.forEach($scope.customerVisit, function (index, i) { if (index.connectionId === customerConnectionId) { $scope.customerVisit[i].chatWith = agentName; } }); apply(); }, //برروز رسانی لیست برای کارشناسان "refreshLeaveChat": function (agentName, customerConnectionId) { angular.forEach($scope.customerVisit, function (index, i) { if (index.connectionId === customerConnectionId) { $scope.customerVisit[i].chatWith =agentName + "---" + " به مکالمه خاتمه داده است "; } }); apply(); } //وضعیت آنلاین بودن کارشناسان , "onlineStatus": function (state) { if (state) { $scope.chatTitle = $scope.options.onlineTitle; $scope.hasOnline = true; $scope.hasOffline = false; } else { $scope.chatTitle = $scope.options.offlineTitle; $scope.hasOffline = true; $scope.hasOnline = false; } $scope.$apply() }, "loginResult": function (status, id, name) { if (status) { $scope.agent.id = id; $scope.agent.name = name; $scope.agent.isOnline = true; $scope.userIsLogin = $scope.agent; $scope.$apply(function () { $location.path("/agent"); }); } else { $scope.agent = null; toastr.error("کارشناسی با این مشخصات وجود ندارد"); return; } }, "newVisit": function (userName, city, country, chatWith, connectionId, firstComment) { var exist = false; angular.forEach($scope.customerVisit, function (index) { if (index.connectionId === connectionId) { exist = true; return; } }); if (!exist) { var date = new Date(); $scope.customerVisit.unshift({ userName: userName, date: date, city: city, country: country, chatWith: chatWith, connectionId: connectionId, firstComment: firstComment }); if ($scope.alarmStatus) { var snd = new Audio("/App/assets/sounds/Sedna.ogg"); snd.play(); } toastr.success("تقاضای جدید دریافت گردید"); apply(); } }, "addMessage": function (id, from, value) { if ($scope.alarmStatus) { var snd = new Audio("/App/assets/sounds/newmsg.mp3"); snd.play(); } $scope.agentUserMsgs = []; var date = new Date(); var position = getid(id); if ($scope.chatConversation.length > 0 && position != null) { $scope.chatConversation[position].sessions.push({ name: from, msg: value, date: date }); } var item = $scope.chatConversation[position]; if (item) { angular.forEach(item.sessions, function (index) { $scope.agentUserMsgs.push({ name: index.name, msg: index.msg, date: date }); }); $scope.chatSessions[position].length = $scope.chatSessions[position].length + 1; } apply(); }, "clientAddMessage": function (id, from) { if ($scope.alarmStatus) { var snd = new Audio("/App/assets/sounds/newmsg.mp3"); snd.play(); } $scope.clientAgentMsg.push({ name: id, msg: from }); apply(); }, "visitorSwitchConversation": function (id, customerName, sessions, sessionId) { sessions = JSON.parse(sessions); var date = new Date(); var sessionList = []; angular.forEach(sessions, function (index) { sessionList.push({ name: index.sender, msg: index.body, date: index.creationTime }); }); $scope.chatConversation.push({ id: sessionId, sessions: sessionList, customerName: customerName, dateStartChat: date.getHours() + ":" + date.getMinutes(), agentName: $scope.agent.name }); $scope.chatSessions.push({ id: sessionId, length: sessions.length, date: date }); }, "receiveTicket": function (items) { angular.forEach(JSON.parse(items), function (index) { $scope.ticketList = []; $scope.ticketList.push(index); }); }, //آرشیو گفته گوهای کارشناس "receiveHistory": function (items) { $scope.agentHistory = []; angular.forEach(JSON.parse(items), function (index) { $scope.agentHistory.push(index); }); apply(); }, //جزییات آرشیو گفتگوها "detailsHistory": function (items) { $scope.historyMsg = []; angular.forEach(JSON.parse(items), function (index) { $scope.historyMsg.push({ name: index.sender, msg: index.body, date: index.creationTime }); }); $("#detailsAgentHistory").modal(); apply(); }, //لیست کارشناسان آنلاین "agentList": function (items) { $scope.agentList = []; angular.forEach(items, function (index) { if ($scope.agent.name != index.Key) { $scope.agentList.push({ name: index.Key, id: index.Value.ConnectionId }); } }); $("#agentList").modal(); apply(); } }, methods: ["agentConnect", "sendTicket", "requestChat", "clientSendMessage", "closeChat", "init", "logVisit", "agentChangeStatus", "engageVisitor", "agentSendMessage", "transfer", "leaveChat", "acceptRequestChat", "leaveChat", "detailsSessoinMessage", "showAgentList", "getAgentHistoryChat" ], errorHandler: function (error) { console.error(error); } }); $scope.myHub.promise.done(function () { $scope.myHub.init(); $scope.myHub.promise.done(function () { }); }); $scope.LeaveChat = function () { $scope.myHub.LeaveChat(); }; $scope.loginAgent = function (userName) { // username :security user username from agent role if (userName == "hossein" || userName == "ali") { $scope.myHub.promise.done(function () { $scope.myHub.agentConnect(userName).then(function (result) { $scope.agent.name = userName; $scope.agent.isOnline = true; }); }); } }; $scope.requestChat = function (msg) { if (!defaultCustomerUserName) { //گرفتن کاربر لاگین شده //ما از آرایه تصادفی استفاده میکنیم var nameDefaultArray = [ 'حسین', 'حسن', 'علی', 'عباس', 'زهرا', 'سمیه' ]; defaultCustomerUserName=nameDefaultArray[Math.floor(Math.random() * nameDefaultArray.length)]; } var userName = defaultCustomerUserName; if (!$scope.chatId) { $scope.chatId = sessionStorage.getItem(chatKey); $http.get("http://ipinfo.io") .success(function (response) { $scope.myHub.logVisit(response.city, response.country, msg, userName); }).error(function (e, status, headers, config) { $scope.myHub.logVisit("Tehran", "Ir", msg, userName) }); $scope.myHub.requestChat(msg); $scope.chatTitle = $scope.options.waitingForOperator; $scope.pendingRequestChat = true; } else { $scope.myHub.clientSendMessage(msg, userName); }; $scope.message = ""; }; $scope.acceptRequestChat = function (customerConnectionId, firstComment, customerName) { $scope.myHub.acceptRequestChat(customerConnectionId, firstComment, customerName); }; $scope.changeAgentStatus = function () { $scope.agent.isOnline = !$scope.agent.isOnline; $scope.myHub.agentChangeStatus($scope.agent.isOnline); }; $scope.detailsChat = function (chatId, userName) { $scope.agentUserMsgs = []; angular.forEach($scope.chatConversation, function (index) { if (index.id === chatId) { $scope.dateStartChat = index.dateStartChat; angular.forEach(index.sessions, function (value) { $scope.agentUserMsgs.push({ name: value.name, msg: value.msg, date: value.date }); }); }; }); $scope.agentChatWithUser = chatId; $scope.customerName = userName; $("#agentUserChat").modal(); }; $scope.ticket = { submit: function () { var name = $scope.ticket.name; var email = $scope.ticket.email; var comment = $scope.ticket.comment; $scope.myHub.sendTicket(name, email, comment); } }; $scope.showHistory = function () { $scope.myHub.getAgentHistoryChat($scope.agent.name); }; $scope.detailsChatHistory = function (id) { $scope.myHub.detailsSessoinMessage(id, $scope.agent.id); }; $scope.agentMsgToUser = function (msg) { var chatId = $scope.agentChatWithUser; var customerName = $scope.customerName; if (!customerName) { angular.forEach($scope.customerVisit, function (index) { if (index.connectionId == chatId) { customerName = index.userName; } }); } if (chatId !== "" && msg !== "") { $scope.myHub.agentSendMessage(chatId, msg, customerName); } //not bind to scope.msg! not correctly work $scope.msg = ""; $("#post-msg").val(""); }; $scope.closeChat = function (chatId) { var item = $scope.chatConversation[getid(chatId)]; $scope.myHub.closeChat(chatId); }; $scope.engageVisitor = function (newAgentId) { var customerId = $scope.customerId; var customerName = $scope.customerName; var clientSessionId = $scope.clientSessionId; $scope.myHub.engageVisitor(newAgentId, customerId, customerName, clientSessionId); $("[data-dismiss=modal]").trigger({ type: "click" }); }; $scope.selectVisitor = function (customerId, customerName, clientSessionId) { $scope.customerId = customerId; $scope.customerName = customerName; $scope.clientSessionId = clientSessionId; $scope.myHub.showAgentList(); }; $scope.setClass = function (item) { if (item === "من") return "question"; else return "response"; }; $scope.setdirectionClass = function (item) { if (item === $scope.agent.name) return { "float": "left" }; else return { "float": "right" }; }; $scope.setArrowClass = function (item) { if (item === $scope.agent.name) return "left-arrow"; else return "right-arrow"; }; $scope.setAlarm = function () { $scope.alarmStatus = !$scope.alarmStatus; }; } }]); app.directive("showtab", function () { return { link: function (scope, element, attrs) { element.click(function (e) { e.preventDefault(); $(element).addClass("active"); $(element).tab("show"); }); } }; }); //زمان ارسال پیام app.directive("timeAgo", function ($q) { return { restrict: "AE", scope: false, link: function (scope, element, attrs) { jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: null, suffixAgo: "پیش", suffixFromNow: "از حالا", seconds: "کمتر از یک دقیقه", minute: "در حدود یک دقیقه", minutes: "%d دقیقه", hour: "حدود یگ ساعت", hours: "حدود %d ساعت ", day: "یک روز", days: "%d روز", month: "حدود یک ماه", months: "%d ماه", year: "حدود یک سال", years: "%d سال", wordSeparator: " ", numbers: [] } var parsedDate = $q.defer(); parsedDate.promise.then(function () { jQuery(element).timeago(); }); attrs.$observe("title", function (newValue) { parsedDate.resolve(newValue); }); } }; });
[HubName("chatHub")] public class ChatHub : Microsoft.AspNet.SignalR.Hub { private readonly ApplicationDbContext _db = new ApplicationDbContext(); private static ConcurrentDictionary<string, UserInformation> _agents; private static List<ChatSessionVm> _chatSessions; private readonly JsonSerializerSettings _settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; public void Init() { _chatSessions = _chatSessions ?? (_chatSessions = new List<ChatSessionVm>()); _agents = _agents ?? (_agents = new ConcurrentDictionary<string, UserInformation>()); Clients.Caller.onlineStatus(_agents.Count(x => x.Value.IsOnline) > 0); } public void AgentConnect(string userName) { //ما برای ساده کردن مقایسه ساده ای انجام دادیم فقط کاربر حسین یا علی میتواند کارشناس باشد if (userName == "hossein" || userName == "ali") { var agent = new UserInformation(); if (_agents.Any(item => item.Key == userName)) { agent = _agents[userName]; agent.ConnectionId = Context.ConnectionId; } else { agent.ConnectionId = Context.ConnectionId; agent.UserName = userName; agent.IsOnline = true; _agents.TryAdd(userName, agent); } Clients.Caller.loginResult(true, agent.ConnectionId, agent.UserName); Clients.All.onlineStatus(_agents.Count(x => x.Value.IsOnline) > 0); } else { Clients.Caller.loginResult(false, null, null); } } public void AgentChangeStatus(bool status) { var agent = _agents.FirstOrDefault(x => x.Value.ConnectionId == Context.ConnectionId).Value; if (agent == null) return; agent.IsOnline = status; Clients.All.onlineStatus(_agents.Count(x => x.Value.IsOnline) > 0); } public void LogVisit(string city, string country, string firstComment, string userName) { foreach (var agent in _agents) { Clients.Client(agent.Value.ConnectionId).newVisit(userName, city, country, null, Context.ConnectionId, firstComment); } } public void AcceptRequestChat(string customerConnectionId, string body, string userName) { var agent = FindAgent(Context.ConnectionId); var session = _chatSessions.FirstOrDefault(item => item.Key.Equals(agent.Key)); if (session == null) { _chatSessions.Add(new ChatSessionVm { Key = agent.Key, Value = new List<string> { customerConnectionId } }); } else { session.Value.Add(customerConnectionId); } Clients.Client(Context.ConnectionId).agentChat(customerConnectionId, body, userName); Clients.Client(customerConnectionId).clientChat(customerConnectionId, agent.Value.UserName); foreach (var item in _agents.Where(item => item.Value.IsOnline)) { Clients.Client(item.Value.ConnectionId).refreshChatWith(agent.Value.UserName, customerConnectionId); } _db.Sessions.Add(new Session { AgentName = agent.Key, CustomerName = userName, CreatedDateTime = DateTime.Now }); _db.SaveChanges(); var message = new Message { CreationTime = DateTime.Now, Sender = agent.Key, Receiver = userName, Body = body, //ConnectionId = _agents.FirstOrDefault(item => item.Value.UserName == userName).Key, Session = _db.Sessions.OrderByDescending(item => item.Id) .FirstOrDefault(item => item.AgentName.Equals(agent.Key) && item.CustomerName.Equals(userName)) }; _db.Messages.Add(message); _db.SaveChanges(); } public void GetAgentHistoryChat(string userName) { var dic = new Dictionary<int, int>(); var lenght = 0; var chats = _db.Sessions.OrderBy(item => item.Id).Include(item => item.Parent) .Where(item => item.AgentName.Equals(userName)).ToList(); foreach (var session in chats) { Result(session, ref lenght); dic.Add(session.Id, lenght); lenght = 0; } if (!chats.Any()) return; var historyResult = chats.Select(item => new AgentViewModel { Id = item.Id, CustomerName = item.CustomerName, Date = item.CreatedDateTime, Lenght = dic.Any(di => di.Key.Equals(item.Id)) ? dic.FirstOrDefault(di => di.Key.Equals(item.Id)).Value : 0, }).OrderByDescending(item => item.Id).ToList(); Clients.Caller.receiveHistory(JsonConvert.SerializeObject(historyResult, new Formatting(), _settings)); } public void DetailsSessoinMessage(int sessionId, string agentId) { var session = _db.Sessions.FirstOrDefault(item => item.Id.Equals(sessionId)); if (session == null) return; var list = new List<Message>(); GetAllMessages(session, list); var result = JsonConvert.SerializeObject(list.OrderBy(item => item.Id), new Formatting(), _settings); Clients.Client(Context.ConnectionId).detailsHistory(result); } public void ClientSendMessage(string body, string userName) { var session = _chatSessions.FirstOrDefault(item => item.Value.Contains(Context.ConnectionId)); if (session == null || session.Key == null) return; var agentId = _agents.FirstOrDefault(item => item.Key.Equals(session.Key)).Value.ConnectionId; Clients.Caller.clientAddMessage("من", body); Clients.Client(agentId).addMessage(Context.ConnectionId, userName, body); var message = new Message { Sender = FindAgent(agentId).Key, Receiver = userName, Body = body, CreationTime = DateTime.Now, Session = FindSession(userName, FindAgent(agentId).Key) }; _db.Messages.Add(message); _db.SaveChanges(); } public void AgentSendMessage(string id, string body, string userName) { var agent = FindAgent(Context.ConnectionId); Clients.Caller.addMessage(id, agent.Value.UserName, body); Clients.Client(id).clientAddMessage(agent.Value.UserName, body); var message = new Message { Sender = agent.Key, Receiver = userName, Body = body, Session = FindSession(agent.Key, userName), CreationTime = DateTime.Now }; _db.Messages.Add(message); _db.SaveChanges(); } public void CloseChat(string id) { var findAgent = FindAgent(Context.ConnectionId); var session = _chatSessions.FirstOrDefault(item => item.Value.Contains(id)); if (session == null) return; Clients.Client(id).clientAddMessage(findAgent.Key, "مکالمه شما با کارشناس مربوطه به اتمام رسیده است"); foreach (var agent in _agents) { Clients.Client(agent.Value.ConnectionId).refreshLeaveChat(agent.Value.UserName, id); } _chatSessions.Remove(session); } public void RequestChat(string message) { Clients.Caller.clientAddMessage("من", message); } public void EngageVisitor(string newAgentId, string cumtomerId, string customerName,string clientSessionId) { #region remove session of current agent var currentAgent = FindAgent(Context.ConnectionId); var currentSession = _chatSessions.FirstOrDefault(item => item.Value.Contains(cumtomerId)); if (currentSession != null) { _chatSessions.Remove(currentSession); } #endregion #region add session to new agent var newAgent = FindAgent(newAgentId); var newSession = _chatSessions.FirstOrDefault(item => item.Key.Equals(newAgent.Key)); if (newSession == null) { _chatSessions.Add(new ChatSessionVm { Key = newAgent.Key, Value = new List<string> { cumtomerId } }); } else { newSession.Value.Add(cumtomerId); } #endregion Clients.Client(currentAgent.Value.ConnectionId).addMessage(cumtomerId, newAgent.Key, "ادامه مکالمه به کارشناس " + newAgent.Key + "مقابل منتقل شد"); Clients.Client(newAgentId).addMessage(cumtomerId, currentAgent.Key, "لطفا مکالمه را ادامه دهید.با تشکر"); Clients.Client(cumtomerId).clientAddMessage(newAgent.Value.UserName, "مکالمه شما با کارشناس زیر برقرار گردید" + newAgent.Key); var session = _db.Sessions.FirstOrDefault (item => item.AgentName.Equals(currentAgent.Value.UserName) && item.CustomerName.Equals(customerName)); if (session != null) { var sessionId = session.Id; var messages = _db.Messages.Where(item => item.Session.Id.Equals(sessionId)); var result = JsonConvert.SerializeObject(messages, new Formatting(), _settings); Clients.Client(newAgentId).visitorSwitchConversation (Context.ConnectionId, customerName, result, clientSessionId); } foreach (var item in _agents.Where(item => item.Value.IsOnline)) { Clients.Client(item.Value.ConnectionId).refreshChatWith(newAgent.Value.UserName, cumtomerId); } _db.Sessions.Add(new Session { AgentName = newAgent.Key, CustomerName = customerName, CreatedDateTime = DateTime.Now, Parent = _db.Sessions.Where(item => item.AgentName.Equals(currentAgent.Key) && item.CustomerName.Equals(customerName)).OrderByDescending(item => item.Id).FirstOrDefault() }); _db.SaveChanges(); } public void ShowAgentList() { Clients.Caller.agentList(_agents.ToList()); } public override Task OnDisconnected(bool stopCalled) { var id = Context.ConnectionId; var isAgent = _agents != null && _agents.Any(item => item.Value.ConnectionId.Equals(id)); if (isAgent) { UserInformation agent; var currentAgentConnectionId = FindAgent(id).Key; if (currentAgentConnectionId == null) return base.OnDisconnected(stopCalled); if (_chatSessions.Any()) { var sessions = _chatSessions.FirstOrDefault(item => item.Key.Equals(currentAgentConnectionId)); //اطلاع دادن به تمام کاربرانی که در حال مکالمه با کارشناس هستند if (sessions != null) { var result = sessions.Value.ToList(); for (var i = 0; i < result.Count(); i++) { var localId = result[i]; Clients.Client(localId).clientAddMessage(currentAgentConnectionId, "ارتباط شما با مشاور مورد نظر قطع شده است"); } } } _agents.TryRemove(currentAgentConnectionId, out agent); Clients.All.onlineStatus(_agents.Count(x => x.Value.IsOnline) > 0); Clients.Client(id).loginResult(false, null, null); } else { if (_chatSessions == null || !_chatSessions.Any(item => item.Value.Contains(id) && _agents == null)) return base.OnDisconnected(stopCalled); var session = _chatSessions.FirstOrDefault(item => item.Value.Contains(id)); if (session == null) return base.OnDisconnected(stopCalled); var agentName = session.Key; var agent = _agents.FirstOrDefault(item => item.Key.Equals(agentName)); if (agent.Key != null) { Clients.Client(agent.Value.ConnectionId).addMessage(id, "کاربر", "اتصال با کاربر قطع شده است"); } } return base.OnDisconnected(stopCalled); } private KeyValuePair<string, UserInformation> FindAgent(string connectionId) { return _agents.FirstOrDefault(item => item.Value.ConnectionId.Equals(connectionId)); } private Session FindSession(string key, string userName) { return _db.Sessions.Where(item => item.AgentName.Equals(key) && item.CustomerName.Equals(userName)) .OrderByDescending(item => item.Id).FirstOrDefault(); } private static void Result(Session parent, ref int lenght) { while (true) { if (parent == null) return; lenght += parent.Messages.Count(); parent = parent.Parent; } } private static List<Message> GetAllMessages(Session node, List<Message> list) { if (node == null) return null; list.AddRange(node.Messages); if (node.Parent != null) { GetAllMessages(node.Parent, list); } return null; } }
<div> <div> <h2> خوش آمدید <span ng-bind="agent.name"> </span> <a ng-click="changeAgentStatus()"> <i ng-if="changeStatus==null" data-placement="bottom" data-trigger="hover " bs-tooltip="options.lock"></i> <i ng-if="changeStatus==true" data-placement="bottom" data-trigger="hover" bs-tooltip="options.unlock"></i> </a> </h2> <div style="float: left"> <a ng-click="setAlarm()"> <i ng-show="alarmStatus" data-placement="bottom" data-trigger="hover " bs-tooltip="options.alarmOn"></i> <i ng-show="!alarmStatus" data-placement="bottom" data-trigger="hover " bs-tooltip="options.alarmOff"></i> </a> <!--<a data-placement="bottom" data-trigger="hover " bs-tooltip="options.conversion" ng-click="showHistory()"><i></i></a>--> <a data-placement="bottom" data-trigger="hover " bs-tooltip="options.edit"><i></i><span></span></a> <a data-placement="bottom" data-trigger="hover " bs-tooltip="options.setting"><i></i></a> <a data-placement="bottom" data-trigger="hover " bs-tooltip="options.signOut" ng-click="LeaveChat()"><i></i><span></span></a> </div> </div> <div> <div> <div id="chat-content"> <div> <ul> <li> <a showtab href="#online-list">آنلاین</a> </li> <li> <a ng-click="showHistory()" showtab href="#conversation">آرشیو گفتگوها</a> </li> </ul> <div> <div id="online-list"> <div> <h2> <i></i><span></span> <span>نمایش آنلاین مراجعه ها</span> </h2> </div> <div> <div id="agent-chat"> <div id="real-time-visits"> <table id="current-visits"> <thead> <tr> <th>نام کاربر</th> <th>زمان اولین تقاضا</th> <th>منطقه</th> <th>پاسخ</th> </tr> </thead> <tbody> <tr id="{{item.connectionId}}" ng-animate="animate" ng-repeat="item in customerVisit "> <td ng-bind="item.userName"></td> <td> <span time-ago title="{{item.date}}"></span> </td> <td> <span ng-bind="item.country"></span> /<span ng-bind="item.city"> </span> </td> <td> <a style="cursor: pointer" ng-if="item.chatWith== null" ng-click="acceptRequestChat(item.connectionId,item.firstComment,item.userName)"> شروع مکالمه </a> <span ng-if="item.chatWith "> وضعیت: <span>در حال مکالمه با</span> <span ng-bind="item.chatWith"></span> <a ng-show="item.chatWith==agent.name" ng-click="selectVisitor(item.connectionId,item.userName,item.connectionId)"> انتقال مکالمه </a> </span> <ul ng-repeat="session in chatSessions track by $index" style="padding:0px;"> <li ng-if="session.id==item.connectionId" id="{{session.id}}"> <div> <p> تاریخ شروع مکالمه: <span time-ago title="{{session.date}}"></span> </p> <p> تعداد پیام ها: <span ng-bind="session.length"></span> </p> </div> <p> <a ng-click="detailsChat(session.id,session.userName)">جزییات </a> <a ng-click="closeChat(session.id)"> خاتمه عملیات </a> </p> </li> </ul> </td> </tr> </tbody> </table> </div> </div> </div> </div> <div id="conversation"> <div> <h2> <i></i><span></span> <span>آرشیو گفتگوهای </span> {{agent.name}} </h2> </div> <div> <div> <table id="current-visits"> <thead> <tr> <th>شناسه مشتری</th> <th>نام مشتری</th> <th>تعداد محاوره ها</th> <th>تاریخ</th> <th>جزئیات</th> </tr> </thead> <tbody> <tr ng-repeat="item in agentHistory track by $index"> <td ng-bind="item.id"></td> <td ng-bind="item.customerName"></td> <th ng-bind="item.lenght"></th> <td><span time-ago title="{{item.date}}"></span></td> <th> <ang-click="detailsChatHistory(item.id)" >مشاهده جزییات گفتگو</a> </th> </tr> </tbody> </table> </div> </div> </div> </div> </div> </div> </div> </div> <div id="detailsAgentHistory" tabindex="-1" role="dialog" aria-labelledby="cmdLabel" aria-hidden="true"> <div> <div> <div> <div> <button type="button" data-dismiss="modal" aria-hidden="true">×</button> </div> <h2> <span></span>تاریخچه گفتگو </h2> </div> <div> <div style="display: block"> <ul ng-repeat="item in historyMsg"> <li> <span ng-bind="item.name" ng-style="setdirectionClass(item.name)"> </span> <span ng-style="setdirectionClass(item.name)"> <span ng-class="setArrowClass(item.name)"></span> <span time-ago title="{{item.date}}"></span> <span> <p ng-bind-html="item.msg | smilies"></p> </span> </span> </li> </ul> </div> </div> </div> </div> </div> <div id="agentList" tabindex="-1" role="dialog" aria-labelledby="cmdLabel" aria-hidden="true"> <div> <div> <div> <div> <button type="button" data-dismiss="modal" aria-hidden="true">×</button> </div> <h2> <span></span>لیست تمام کارشناسان </h2> </div> <div> <div style="display: block;"> <div ng-show="agentList.length==0"> کارشناس آنلاینی وجود ندارد </div> <ul ng-repeat="item in agentList"> <li> <span> <a ng-click="engageVisitor(item.id)">{{item.name}}</a> </span> </li> </ul> </div> </div> </div> </div> </div> <div id="agentUserChat" tabindex="-1" role="dialog" aria-labelledby="cmdLabel" aria-hidden="true"> <div> <div> <div> <div> <button type="button" data-dismiss="modal" aria-hidden="true">×</button> </div> <h2> <span></span>گفتگو </h2> </div> <div> <div> <div> <div style="display: block;"> <label>شروع چت در </label>: <span ng-bind="dateStartChat"></span> <ul> <li ng-repeat="item in agentUserMsgs"> <span ng-bind="item.name" ng-style="setdirectionClass(item.name)"> </span> <span ng-style="setdirectionClass(item.name)"> <span ng-class="setArrowClass(item.name)"></span> <span time-ago title="{{item.date}}"></span> <span> <p ng-bind-html="item.msg | smilies"></p> </span> </span> </li> </ul> <div> <div> <textarea id="post-msg" ng-model="msg" placeholder="متن خود را وارد نمایید" style="overflow: hidden; word-wrap: break-word; resize: horizontal; height: 80px; max-width: 100%"></textarea> <span smilies-selector="msg" smilies-placement="right" smilies-title="Smilies"></span> </div> <div style="text-align: center; margin-top: 5px"> <button ng-click="agentMsgToUser(msg)">ارسال</button> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div>
<html ng-app="app"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Live Support</title> <link href="~/Content/bootstrap-rtl.css" rel="stylesheet" /> <link href="~/Scripts/smilies/angular-smilies-embed.css" rel="stylesheet" /> <link href="~/Content/font-awesome.css" rel="stylesheet" /> <link href="~/Content/toastr.css" rel="stylesheet" /> <link href="~/Content/liveSupport.css" rel="stylesheet" /> <script src="~/Scripts/jquery-1.10.2.js"></script> <script src="~/Scripts/toastr.js"></script> <script src="~/Scripts/jquery.timeago.js"></script> <script src="~/Scripts/angular.js"></script> <script src="~/Scripts/angular-animate.js"></script> <script src="~/Scripts/angular-sanitize.js"></script> <script src="~/Scripts/angular-route.js"></script> <script src="~/Scripts/angular-strap.js"></script> <script src="~/Scripts/angular-strap.tpl.js"></script> <script src="~/Scripts/smilies/angular-smilies.js"></script> <script src="~/Scripts/jquery.signalR-2.2.0.js"></script> <script src="~/Scripts/angular-signalr-hub.js"></script> <script src="~/app/app.js"></script> @Scripts.Render("~/bundles/bootstrap") </head> <body ng-controller="ChatCtrl"> <div ng-view> </div> <div id="chat-box-header" ng-click="boxheader()"> {{chatTitle}} </div> <div id="chat-box"> <div ng-show="hasOnline"> <div id="style-1" style="min-height:100px;"> <div ng-repeat="item in clientAgentMsg track by $index"> <span ng-class="setClass(item.name)"> {{item.name}} </span> <br /> <p ng-bind-html="item.msg | smilies"></p> </div> </div> <div> <label>پیام</label> <div style="text-align: left; clear: both"> <a data-placement="top" data-trigger="hover " bs-tooltip="options.alarm" ng-click="alarm()"><i></i></a> <a data-placement="top" data-trigger="hover " bs-tooltip="options.signOut" href="signOut()"><i></i><span></span></a> <a data-placement="top" data-trigger="hover " bs-tooltip="options.upload" href="fileupload()"> <span><i></i></span> </a> </div> <div> <textarea style="height: 150px; max-height: 160px;" ng-model="message" placeholder=" متن خود را وارد نمایید"></textarea> <span smilies-selector="message" smilies-placement="right" smilies-title="Smilies"></span> </div> </div> <div style="text-align: center"> <button type="button" ng-disabled="pendingRequestChat" ng-click="requestChat(message)">ارسال </button> </div> </div> <div ng-show="hasOffline"> <div> <form name="Ticket" id="form1"> <fieldset> <div> <label>نام</label> <input name="email" ng-model="ticket.name" > </div> <div> <label>ایمیل</label> <input name="email" ng-model="ticket.email" > </div> <div> <label>پیام</label> </div> <div> <textarea ng-model="ticket.comment" placeholder="متن خود را وارد نمایید"></textarea> <span smilies-selector="ticket.comment" smilies-placement="right" smilies-title="Smilies"></span> </div> </fieldset> <div style="text-align: center"> <button type="button" ng-click="ticket.submit(ticket)"> ارسال </button> </div> </form> </div> </div> </div> </body> </html>
نکات تکمیلی :
app.MapSignalR();
آموزش ساخت یک بلاگ با Blazor .Net 8
In this video we will build a complete full stack Blog Web App using the new Blazor Web App with SSR with .Net 8 | Step by Step | From Scratch to End
You will learn: New Static Server Side Rendering with Blazor, QuickGrid, Enhanced Navigation, Enhanced Forms, Stream Rendering, Image Preview & Upload, Identity Authentication, Interactive Server Render Mode for Admin Panel, and much more
مقایسه (sortable Guid)GUID , UUID
Pros and cons of Database identity
Nice to work with in URLs
Limiting, as they require a trip to the database, which precludes some patterns
Can be tricky to return IDs when inserting in some cases (EF Core etc solves this)
Can cause contention in high throughput scenarios. May make scaling out impossible
CoffeeScript #6
Classes
Inheritance & Super
شما میتوانید به راحتی از کلاسهای دیگری که نوشتهاید، با استفاده از کلمهی کلیدی ،extends ارث بری کنید:
class Animal constructor: (@name) -> alive: -> true class Parrot extends Animal constructor: -> super("Parrot") dead: -> not @alive()
همانطوری که در مثال بالا مشاهده میکنید، در کلاس Parrot در تابع constructor، تابع super فراخوانی شده است. با استفاده از کلمهی کلیدی super میتوان تابع سازندهی کلاس پدر را فراخوانی کرد. نتیجهی کامپایل super در مثال بالا به این صورت میشود:
Parrot.__super__.constructor.call(this, "Parrot");
در صورتیکه تابع constructor را در کلاس فرزند ننوشته باشید، به طور پیش فرض CoffeeScript سازنده کلاس پدر را فراخوانی میکند.
CoffeeScript با استفاده از prototypal inheritance، به صورت خودکار تمامی خصوصیات کلاس پدر، به فرزندان انتقال پیدا میکند. این ویژگی سبب داشتن کلاسهای پویا میشود. برای درک بهتر این موضوع، فرض کنید که خصوصیتی را به کلاس پدر بعد از ارث بری کلاس فرزند اضافه میکنید. خصوصیت اضافه شده به تمامی فرزندان کلاس پدر به صورت خودکار اضافه میشود.
class Animal constructor: (@name) -> class Parrot extends Animal Animal::rip = true parrot = new Parrot("Macaw") alert("This parrot is no more") if parrot.rip
Mixins
Mixins توسط CoffeeScript پشتیبانی نمیشود و برای همین نیاز است که این قابلیت را برای خودمان پیاده سازی کنیم، به مثال زیر توجه کنید.extend = (obj, mixin) -> obj[name] = method for name, method of mixin obj include = (klass, mixin) -> extend klass.prototype, mixin # Usage include Parrot, isDeceased: true alert (new Parrot).isDeceased
var extend, include; extend = function(obj, mixin) { var method, name; for (name in mixin) { method = mixin[name]; obj[name] = method; } return obj; }; include = function(klass, mixin) { return extend(klass.prototype, mixin); }; include(Parrot, { isDeceased: true }); alert((new Parrot).isDeceased);
Extending classes
Mixins خیلی مرتب و خوب است اما خیلی شیء گرا نیست؛ در عوض امکان ادغام را در کلاسهای CoffeeScript ایجاد میکند. برای اینکه اصول شیء گرایی را بخواهیم رعایت کنیم و ویژگی ادغام را نیز داشته باشیم، کلاسی با نام Module را پیاده سازی میکنیم و تمامی کلاسهایی را که میخواهیم ویژگی ادغام را داشته باشند، از آن ارث بری میکنیم.
moduleKeywords = ['extended', 'included'] class Module @extend: (obj) -> for key, value of obj when key not in moduleKeywords @[key] = value obj.extended?.apply(@) this @include: (obj) -> for key, value of obj when key not in moduleKeywords # Assign properties to the prototype @::[key] = value obj.included?.apply(@) this
classProperties = find: (id) -> create: (attrs) -> instanceProperties = save: -> class User extends Module @extend classProperties @include instanceProperties # Usage: user = User.find(1) user = new User user.save()
همچنین برای خلاصه نویسی بیشتر میتوان از این الگو استفاده کرد (ساده و زیبا).
ORM = find: (id) -> create: (attrs) -> extended: -> @include save: -> class User extends Module @extend ORM