#OS junk files [Tt]humbs.db *.DS_Store #Visual Studio files *.[Oo]bj *.user *.aps *.pch *.vspscc *.vssscc *_i.c *_p.c *.ncb *.suo *.tlb *.tlh *.bak *.[Cc]ache *.ilk *.log *.lib *.sbr *.sdf *.opensdf *.unsuccessfulbuild ipch/ obj/ [Bb]in [Dd]ebug*/ [Rr]elease*/ Ankh.NoLoad #MonoDevelop *.pidb *.userprefs #Tooling _ReSharper*/ *.resharper [Tt]est[Rr]esult* *.sass-cache #Project files [Bb]uild/ #Subversion files .svn # Office Temp Files ~$* #NuGet packages/ #ncrunch *ncrunch* *crunch*.local.xml # visual studio database projects *.dbmdl #Test files *.testsettings
برای تهیه یک RadioButtonList نیز میتوان از همان نکتهی CheckBoxList استفاده کرد: نام عناصر radio button اضافه شده به صفحه را یکسان وارد میکنیم. به این ترتیب یک گروه تشکیل خواهد شد و زمانیکه اطلاعات این عناصر به سرور ارسال میشود، اینبار بجای یک آرایه، تنها مقدار کنترل انتخاب شده، ارسال میگردد. یک مثال:
یک پروژه جدید و خالی ASP.NET MVC را آغاز کنید. سپس کنترلر Home و View خالی Index را نیز ایجاد نمائید. محتویات این دو را به نحو زیر تغییر دهید:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<fieldset>
<legend>HandleForm1 (Normal)</legend>
@using (Html.BeginForm(actionName: "HandleForm1", controllerName: "Home"))
{
@:your favorite tech: <br />
@Html.RadioButton(name: "tech", value: ".NET", isChecked: true) @:DOTNET <br />
@Html.RadioButton(name: "tech", value: "JAVA", isChecked: false) @:JAVA <br />
@Html.RadioButton(name: "tech", value: "PHP", isChecked: false) @:PHP <br />
<input type="submit" value="Submit" />
}
</fieldset>
using System.Collections.Generic;
using System.Web.Mvc;
namespace MvcApplication23.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult HandleForm1(string tech)
{
return RedirectToAction("Index");
}
}
}
در اینجا سه RadioButton با نامی یکسان در صفحه اضافه شدهاند. سپس داخل متد HandleForm1 یک breakpoint قرار دهید. اکنون برنامه را اجرا کنید و فرم را به سرور ارسال نمائید. پارامتر tech با value عنصر انتخابی مقدار دهی خواهد شد.
تهیه یک RadioButtonList عمومی
اطلاعات فوق را میتوان تبدیل به یک HtmlHelper با قابلیت استفاده مجدد نیز نمود:
@helper RadioButtonList(string groupName, IEnumerable<System.Web.Mvc.SelectListItem> items)
{
<div class="RadioButtonList">
@foreach (var item in items)
{
@item.Text
<input type="radio" name="@groupName"
value="@item.Value"
@if (item.Selected) { <text>checked="checked"</text> }
/>
<br />
}
</div>
}
برای مثال یک فایل را در مسیر app_code\Helpers.cshtml ایجاد کرده و اطلاعات فوق را به آن اضافه نمائید.
اینبار برای استفاده از آن خواهیم داشت:
using System.Collections.Generic;
using System.Web.Mvc;
namespace MvcApplication23.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
ViewBag.Tags = new[]
{
new SelectListItem { Text = ".NET", Value = "Val1", Selected = true },
new SelectListItem { Text = "JAVA", Value = "Val2", Selected = false },
new SelectListItem { Text = "PHP", Value = "Val3", Selected = false }
};
return View();
}
[HttpPost]
public ActionResult HandleForm2(string preferredTechnology)
{
return RedirectToAction("Index");
}
}
}
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<fieldset>
<legend>HandleForm2 (Helper)</legend>
@using (Html.BeginForm(actionName: "HandleForm2", controllerName: "Home"))
{
@:your favorite tech: <br />
@Helpers.RadioButtonList("preferredTechnology", (SelectListItem[])ViewBag.Tags)
<input type="submit" value="Submit" />
}
</fieldset>
متد سفارشی تهیه شده، یک آرایه از SelectListItem ها را دریافت کرده و به صورت خودکار تبدیل به RadioButtonList میکند. بر اساس نام آن میتوان به مقدار انتخاب شده ارسالی به سرور در کنترلر مرتبط، دسترسی یافت.
تهیه یک Templated helper سفارشی
در عمل زمانیکه با مدلها کار میکنیم و اطلاعات برنامه قرار است Strongly typed باشند، مرسوم است لیستی از انتخابها را به صورت یک enum تعریف کنند. برای مثال مدل زیر را به برنامه اضافه کنید:
using System.ComponentModel.DataAnnotations;
namespace MvcApplication23.Models
{
public enum Gender
{
[Display(Name = "مرد")]
Male,
[Display(Name = "زن")]
Female,
}
public class User
{
[ScaffoldColumn(false)]
public int Id { set; get; }
[Display(Name = "نام")]
public string Name { set; get; }
[Display(Name = "جنسیت")]
[UIHint("EnumRadioButtonList")]
public Gender Gender { set; get; }
}
}
قصد داریم یک Templated helper سفارشی را به نام EnumRadioButtonList، ایجاد کنیم تا در زمان فراخوانی متد Html.EditorForModel، به صورت خودکار enum تعریف شده را به صورت یک RadioButtonList نمایش دهد.
برای این منظور فایل جدید Views\Shared\EditorTemplates\EnumRadioButtonList.cshtml را به برنامه اضافه کنید. محتوای آنرا به نحو زیر تغییر دهید:
@using System.ComponentModel.DataAnnotations
@using System.Globalization
@model Enum
@{
Func<Enum, string> getDescription = enumItem =>
{
var type = enumItem.GetType();
var memInfo = type.GetMember(enumItem.ToString());
if (memInfo != null && memInfo.Any())
{
var attrs = memInfo[0].GetCustomAttributes(typeof(DisplayAttribute), false);
if (attrs != null && attrs.Any())
return ((DisplayAttribute)attrs[0]).GetName();
}
return enumItem.ToString();
};
var listItems = Enum.GetValues(Model.GetType())
.OfType<Enum>()
.Select(enumItem =>
new SelectListItem()
{
Text = getDescription(enumItem),
Value = enumItem.ToString(),
Selected = enumItem.Equals(Model)
});
string prefix = ViewData.TemplateInfo.HtmlFieldPrefix;
ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
int index = 0;
foreach (var li in listItems)
{
string fieldName = string.Format(CultureInfo.InvariantCulture, "{0}_{1}", prefix, index++);
<div class="editor-radio">
@Html.RadioButton(prefix, li.Value, li.Selected, new { @id = fieldName })
@Html.Label(fieldName, li.Text)
</div>
}
ViewData.TemplateInfo.HtmlFieldPrefix = prefix;
}
در اینجا به کمک Reflection به اطلاعات enum دریافتی دسترسی خواهیم داشت. بر این اساس میتوان نام عناصر آنرا یافت و تبدیل به یک RadioButtonList کرد. البته کار به همینجا ختم نمیشود. در این بین باید دقت داشت که ممکن است از ویژگی Display (مانند مدل نمونه فوق) بر روی تک تک عناصر یک enum نیز استفاده شود. به همین جهت این مورد نیز باید پردازش گردد.
نهایتا برای استفاده از این Templated helper سفارشی، کنترلر و View برنامه را به نحو زیر میتوان تغییر داد:
using System.Collections.Generic;
using System.Web.Mvc;
using MvcApplication23.Models;
namespace MvcApplication23.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
var user = new User { Id = 1, Name = "name 1", Gender = Gender.Male };
return View(user);
}
[HttpPost]
public ActionResult HandleForm3(User user)
{
return RedirectToAction("Index");
}
}
}
@model MvcApplication23.Models.User
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<fieldset>
<legend>HandleForm3 (EditorForModel)</legend>
@using (Html.BeginForm(actionName: "HandleForm3", controllerName: "Home"))
{
@Html.EditorForModel()
<input type="submit" value="Submit" />
}
</fieldset>
برای استفاده از یک templated helper سفارشی چندین روش وجود دارد:
الف) همانند مثال فوق از ویژگی UIHint استفاده شود.
ب) نام فایل را به enum.cshtml تغییر دهیم. به این ترتیب از این پس کلیه enumها در صورت استفاده از متد Html.EditorForModel، به صورت خودکار تبدیل به یک RadioButtonList میشوند.
ج) متد زیر نیز همین کار را انجام میدهد:
@Html.EditorFor(model => model.EnumProperty, "EnumRadioButtonList")
ارسال به JsonResult
public JsonResult PdfReport(ReportViewModel model) { if (model == null) return Json(new { success = false }, JsonRequestBehavior.AllowGet); var report = new GeneratePdfReport(); report.CreatePdfReport(AutoMapperHelper.Map<ReportViewModel, Report>(model)); return Json(new { success = true }, JsonRequestBehavior.AllowGet); }
fileName = HttpUtility.UrlEncode(fileName, Encoding.UTF8); data.FlushInBrowser(fileName, FlushType.Inline);
uploader.bind('FileUploaded', function(up, file, info) { var obj = JSON.parse(info.response); $('form#quoteRequest').append('<input type="hidden" name="file_name" value="' + obj.result.cleanFileName + '" />'); //note obj.result.cleanFileName instead obj.cleanFileName });
البته یک نمونه در اینترنت پیدا کردم (البته پاسخ Copilot بوده) که متد را بصورت Generic تعریف کرده بود که برای انجام Dynamic Select روش زیر را بکاربرده بود:
IQueryable<TResult> results = query.SelectMany(x => selectProperties.Select(prop => prop.Compile()(x)));
تا اینجا هیچ مشکلی وجود ندارد ولی به محض آنکه در خط بعدی متد ToList اجرا میشه با پیغام خطا مواجه میشم:
An exception was thrown while attempting to evaluate a LINQ query parameter expression. See the inner exception for more information. To show additional information call 'DbContextOptionsBuilder.EnableSensitiveDataLogging'.
کد کامل Copilot:
public async Task<List<LineJoint2>> GetAsync2( Expression<Func<LineJoint, bool>> filter, List<Expression<Func<LineJoint, object>>> includes, List<Func<IIncludableQueryable<LineJoint, object>, IIncludableQueryable<LineJoint, object>>> thenIncludes, Expression<Func<IQueryable<LineJoint>, IOrderedQueryable<LineJoint>>> orderBy, int pageSize, int pageNumber, bool trackChanges = true, CancellationToken cancellationToken = default) { var query = context.LineJoints.AsQueryable(); // Apply filter if (filter != null) query = query.Where(filter); // Include related entities foreach (var include in includes) query = query.Include(include); // Apply thenIncludes foreach (var thenInclude in thenIncludes) query = thenInclude(query); // Project properties dynamically var projectedQuery = query.Select(x => new LineJoint2 { Id = x.Id, JointNo = x.JointNo // Add other properties from LineJoint as needed }); // Apply ordering if (orderBy != null) projectedQuery = orderBy.Compile()(projectedQuery); // Apply pagination var pagedQuery = projectedQuery.Skip((pageNumber - 1) * pageSize).Take(pageSize); // Execute the query return await (trackChanges ? pagedQuery : pagedQuery.AsNoTracking()).ToListAsync(cancellationToken); }
public class ValidationHelper { public static IList<ValidationResult> GetErrors(object instance) { var res = new List<ValidationResult>(); Validator.TryValidateObject(instance, new ValidationContext(instance, null, null), res, true); return res; } public static IList<ValidationResult> ValidateProperty(object value, string propertyName) { if (string.IsNullOrEmpty(propertyName)) throw new ArgumentException("Invalid property name", propertyName); var propertyValue = value.GetType().GetProperty(propertyName).GetValue(value, null); var results = new List<ValidationResult>(); var context = new ValidationContext(value, null, null) { MemberName = propertyName }; Validator.TryValidateProperty(propertyValue, context, results); return results; } }
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class RequierdٍExAttribute : ValidationAttribute { public RequierdٍExAttribute(string ErrorMessage) : base() { this.ErrorMessage = ErrorMessage; } public override bool IsValid(object value) { if (value == null) return false; return true; }
آشنایی با Jaeger
برای پیاده سازی distributed tracing، میتوانیم از ابزار متن باز و محبوب Jaeger (با تلفظ یِگِر) که ابتدا توسط شرکت Uber منتشر شد، استفاده کنیم. نحوه کارکرد Jaeger بصورت زیر میباشد:
سادهترین روش برای راهاندازی Jager، استفاده از داکر ایمیج All in one که شامل ماژول های agent ، collector، query و ui است. پورت 6831 مربوط به agent و پورت 16686 مربوط به ui میباشد. برای جزئیات مربوط به ماژولهای مختلف از این لینک استفاده کنید.
docker run -d -p 6831:6831/udp -p 6832:6832/udp -p 14268:14268 -p 14250:14250 -p 16686:16686 -p 5778:5778 --name jaeger jaegertracing/all-in-one:latest
بعد از اجرای دستور بالا، اطلاعات مربوط به سرویسها و trace ها در ماژول Jager UI با آدرس http://localhost:16686 قابل مشاهده است.
جهت استفاده از Jaeger از پروژه تستی که شامل دو سرویس User و Gateway میباشد، استفاده میکنیم. در سرویس User، متد AddUser در صورت عدم وجود کاربر در دیتابیس، اطلاعات کاربر از گیتهاب را دریافت و در دیتابیس ذخیره میکند. سرویس Gateway از Ocelot برای مسیردهی درخواستها استفاده میکند. برای آشنایی با ocelot این پست را مطالعه نمایید.
public async Task<ApiResult<Models.User>> AddUserAsync(string username) { var result = new ApiResult<Models.User>(); var user = await _applicationDbContext.Users.FirstOrDefaultAsync(x => x.Login == username); if (user is null) { try { var url = string.Format(_appConfig.Github.ProfileUrl, username); var apiResult = await _httpClient.GetStringAsync(url); var userDto = JsonSerializer.Deserialize<UserDto>(apiResult); user = _mapper.Map<Models.User>(userDto); await _applicationDbContext.Users.AddAsync(user); await _applicationDbContext.SaveChangesAsync(); result.Result = user; result.Message = "User successfully Created"; return result; } catch (Exception e) { result.Message = "User not found"; return result; } } result.Message = "User already exist"; result.Result = user; return result; }
برای ثبت Trace مربوط به درخواستها در Jaeger ، بعد از نصب پکیجهای Jaeger و OpenTracing.Contrib.NetCore در هر دو سرویس، در کانفیگ هریک از سرویسها مورد زیر را اضافه میکنیم:
"JaegerConfig": { "Host": "localhost", "Port": 6831, "IsEnabled": true, "SamplingRate": 0.5 }
و برای اضافه شدن tracer به برنامه از متد الحاقی زیر استفاده میکنیم:
public static class Extensions { public static void AddJaeger(this IServiceCollection services, IConfiguration configuration) { var config = configuration.GetSection("JaegerConfig").Get<JaegerConfig>(); if (!(config?.IsEnabled ?? false)) return; if (string.IsNullOrEmpty(config?.Host)) throw new Exception("invalid JaegerConfig"); services.AddSingleton<ITracer>(serviceProvider => { string serviceName = Assembly.GetEntryAssembly()?.GetName().Name; ILoggerFactory loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>(); var sampler = new ProbabilisticSampler(config.SamplingRate); var reporter = new RemoteReporter.Builder() .WithLoggerFactory(loggerFactory) .WithSender(new UdpSender(config.Host, config.Port, 0)) .WithFlushInterval(TimeSpan.FromSeconds(15)) .WithMaxQueueSize(300) .Build(); ITracer tracer = new Tracer.Builder(serviceName) .WithLoggerFactory(loggerFactory) .WithSampler(sampler) .WithReporter(reporter) .Build(); GlobalTracer.Register(tracer); return tracer; }); services.AddOpenTracing(); } }
برای ثبت traceها استراتژیهای متفاوتی وجود دارد. در اینجا از ProbabilisticSampler استفاده شدهاست که در سازندهی آن میتوان درصد ثبت Traceها را مقدار دهی کرد. در نهایت این متد الحاقی را در Startup اضافه میکنیم:
builder.Services.AddJaeger(builder.Configuration);
بعد از اجرای پروژه و فراخوانی https://localhost:6000/gateway/Users/Add ، سرویس Gateway، درخواست را به سرویس User ارسال میکند و این سرویسها در Jaeger UI قابل مشاهده هستند.
جهت مشاهده trace ها ، سرویس مورد نظر را انتخاب و روی Find Traces کلیک کنید. با کلیک روی Trace مورد نظر، جزئیات فعالیت هایی مثل فراخوانی سرویس و مراجعه به دیتابیس قابل مشاهده است.
برای اضافه کردن لاگ سفارشی به یک span، میتوان از اینترفیس ITracer استفاده کرد:
private readonly IUserService _userService; private readonly ITracer _tracer; public UsersController(IUserService userService, ITracer tracer) { _userService = userService; _tracer = tracer; }
[HttpPost] public async Task<ActionResult> AddUser(AddUserDto model) { var actionName = ControllerContext.ActionDescriptor.DisplayName; using var scope = _tracer.BuildSpan(actionName).StartActive(true); scope.Span.Log($"Add user log username: {model.Username}"); return Ok(await _userService.AddUserAsync(model.Username)); }
کدهای مربوط به این مطلب در اینجا قابل دسترسی است.
مراحل نحوه اجرای برنامه:
نصب کتابخانههای زیر:
//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();
using System; using System.Diagnostics; using System.IO; using System.Management; using Microsoft.Win32; namespace PdfFilePrinter { /// <summary> /// Executes the Adobe Reader and prints a file while suppressing the Acrobat print /// dialog box, then terminating the Reader. /// </summary> public class AcroPrint { /// <summary> /// The Adobe Reader or Adobe Acrobat path such as 'C:\Program Files\Adobe\Adobe Reader X\AcroRd32.exe'. /// If it's not specified, the InstalledAdobeReaderPath property value will be used. /// </summary> public string AdobeReaderPath { set; get; } /// <summary> /// Returns the default printer name. /// </summary> public string DefaultPrinterName { get { var query = new ObjectQuery("SELECT * FROM Win32_Printer"); using (var searcher = new ManagementObjectSearcher(query)) { foreach (var mo in searcher.Get()) { if (((bool?)mo["Default"]) ?? false) return mo["Name"] as string; } } return string.Empty; } } /// <summary> /// The name and path of the PDF file to print. /// </summary> public string PdfFilePath { set; get; } /// <summary> /// Name of the printer such as '\\PrintServer\HP LaserJet'. /// If it's not specified, the DefaultPrinterName property value will be used. /// </summary> public string PrinterName { set; get; } /// <summary> /// Returns the HKEY_CLASSES_ROOT\Software\Adobe\Acrobat\Exe value. /// If AcroRd32.exe does not exist, returns string.Empty /// </summary> public string InstalledAdobeReaderPath { get { var acroRd32Exe = Registry.ClassesRoot.OpenSubKey(@"Software\Adobe\Acrobat\Exe", writable: false); if (acroRd32Exe == null) return string.Empty; var exePath = acroRd32Exe.GetValue(string.Empty) as string; if (string.IsNullOrEmpty(exePath)) return string.Empty; exePath = exePath.Trim(new[] { '"' }); return File.Exists(exePath) ? exePath : string.Empty; } } /// <summary> /// Executes the Adobe Reader and prints a file while suppressing the Acrobat print /// dialog box, then terminating the Reader. /// </summary> /// <param name="timeout">The amount of time, in milliseconds, to wait for the associated process to exit. The maximum is the largest possible value of a 32-bit integer, which represents infinity to the operating system.</param> public void PrintPdfFile(int timeout = Int32.MaxValue) { if (!File.Exists(PdfFilePath)) throw new ArgumentException(PdfFilePath + " does not exist."); var args = string.Format("/N /T \"{0}\" \"{1}\"", PdfFilePath, getPrinterName()); var process = startAdobeProcess(args); if (!process.WaitForExit(timeout)) process.Kill(); } private Process startAdobeProcess(string arguments = "") { var startInfo = new ProcessStartInfo { FileName = this.getExePath(), Arguments = arguments, CreateNoWindow = true, ErrorDialog = false, UseShellExecute = false, Verb = "print" }; return Process.Start(startInfo); } private string getPrinterName() { var printer = PrinterName; if (string.IsNullOrEmpty(printer)) printer = DefaultPrinterName; if (string.IsNullOrEmpty(printer)) throw new ArgumentException("Please set the PrinterName."); return printer; } private string getExePath() { var exePath = AdobeReaderPath; if (string.IsNullOrEmpty(exePath) || !File.Exists(exePath)) exePath = InstalledAdobeReaderPath; if (string.IsNullOrEmpty(exePath)) throw new ArgumentException("Please set the full path of the AcroRd32.exe or Acrobat.exe."); return exePath; } } }
توضیحات:
استفاده ابتدایی از کلاس فوق به نحو زیر است:
new AcroPrint { PdfFilePath = @"D:\path\test.pdf" }.PrintPdfFile();
ملاحظات:
- کدهای فوق نیاز به ارجاعی به اسمبلی استاندارد System.Management.dll نیز دارند.
- اگر علاقمند بودید که چاپگر خاصی را معرفی کنید (برای مثال یک چاپگر تعریف شده در شبکه)، میتوانید خاصیت PrinterName را مقدار دهی نمائید.
- محل نصب Adobe reader از رجیستری ویندوز استخراج میشود. اما اگر محل نصب برنامه استاندارد نبود، نیاز است خاصیت AdobeReaderPath مقدار دهی گردد.
- تحت هر شرایطی برنامه Adobe reader ظاهر خواهد شد؛ حتی اگر در حین آغاز پروسه سعی در مخفی کردن پنجره آن نمائید. اینکار به عمد جهت مسایل امنیتی در این برنامه درنظر گرفته شده است تا کاربر بداند که پروسه چاپ آغاز شده است.