I am building(still learning) a web application in ASP.NET WebForms with C#.We have a Centralized Database and all Clients are connected to the Database through a same static IP.Each of the Clients have their own
Unique Office ID.We have 16 Offices each having their own office ID.Every Day we update new features with new build.Instead of sending chat message
to individual client about the new changes/updates/features, can we make it send as a broadcast message like from all the offices i have mentioned
there is a corporate office with OfficeId=14.So the moment the User from other office Logs in, he/she should see a pop-up notification message
about the changes.Is it possible to make say a form to enter the details about the changes and the moment the user from the corporte office saves it, it shows in the index page of
all the clients?
I did a lot of research on this, but couldnt get a solid explanation.This might be a duplicate or lame question for all the experts out here,please
bear with me.
Check this link ASP.Net SignalR: Building a Simple Real-Time Chat Application
from the ChatHub class and Use following Code.
public class ChatHub : Hub
{
static ConcurrentDictionary<string, string> dic = new ConcurrentDictionary<string, string>();
public void Send(string name, string message)
{
Clients.All.broadcastMessage(name, message);
}
public void Notify(string name, string id)
{
if (dic.ContainsKey(name))
{
Clients.Caller.differentName();
}
else
{
dic.TryAdd(name, id);
foreach (KeyValuePair<String, String> entry in dic)
{
Clients.Caller.online(entry.Key);
}
Clients.Others.enters(name);
}
}
public override Task OnDisconnected()
{
var name = dic.FirstOrDefault(x => x.Value == Context.ConnectionId.ToString());
string s;
dic.TryRemove(name.Key, out s);
return Clients.All.disconnected(name.Key);
}
}
And in HTML + javascript
<script type="text/javascript">
$(function () {
showModalUserNickName();
});
function showModalUserNickName() {
$("#dialog").dialog({
modal: true,
buttons: {
Ok: function () {
$(this).dialog("close");
startChartHub();
}
}
});
}
function startChartHub() {
var chat = $.connection.chatHub;
// Get the user name.
$('#nickname').val($('#nick').val());
chat.client.differentName = function (name) {
showModalUserNickName();
return false;
// Prompts for different user name
$('#nickname').val($('#nick').val());
chat.server.notify($('#nickname').val(), $.connection.hub.id);
};
chat.client.online = function (name) {
// Update list of users
if (name == $('#nickname').val())
$('#onlineusers').append('<div class="border" style="color:green">You: ' + name + '</div>');
else {
$('#onlineusers').append('<div class="border">' + name + '</div>');
}
};
chat.client.enters = function (name) {
$('#chatlog').append('<div style="font-style:italic;"><i>' + name + ' joins the conversation</i></div>');
$('#onlineusers').append('<div class="border">' + name + '</div>');
};
// Create a function that the hub can call to broadcast chat messages.
chat.client.broadcastMessage = function (name, message) {
//Interpret smileys
message = message.replace(":)", "<img src=\"/images/smile.gif\" class=\"smileys\" />");
message = message.replace("lol", "<img src=\"/images/laugh.gif\" class=\"smileys\" />");
message = message.replace(":o", "<img src=\"/images/cool.gif\" class=\"smileys\" />");
//display the message
$('#chatlog').append('<div class="border"><span style="color:red">' + name + '</span>: ' + message + '</div>');
};
chat.client.disconnected = function (name) {
//Calls when someone leaves the page
$('#chatlog').append('<div style="font-style:italic;"><i>' + name + ' leaves the conversation</i></div>');
$('#onlineusers div').remove(":contains('" + name + "')");
}
// Start the connection.
$.connection.hub.start().done(function () {
//Calls the notify method of the server
chat.server.notify($('#nickname').val(), $.connection.hub.id);
$('#btnsend').click(function () {
// Call the Send method on the hub.
chat.server.send($('#nickname').val(), $('#message').val());
// Clear text box and reset focus for next comment.
$('#message').val('').focus();
});
});
}
</script>
<div id="container">
<input type="hidden" id="nickname" />
<div id="chatlog"></div>
<div id="onlineusers">
<b>Online Users</b>
</div>
<div id="chatarea">
<div class="messagelog">
<textarea spellcheck="true" id="message" class="messagebox"></textarea>
</div>
<div class="actionpane">
<input type="button" id="btnsend" value="Send" />
</div>
<div class="actionpane">
</div>
</div>
<div id="dialog" title="Enter your name to start a chat.">
<input type="text" id="nick" />
</div>
Related
I'm working on chat application with c# as a server and angular as a client. I'm using SignalR for chatting between clients. My app is working well but I'm having trouble with getting a list of all connected users.
I know for sure that the client is reaching the server with http get request (after connecting to SignalR) because I console.log the connected users list and I'm getting the data:
The problem is that when I try to print the items of the list - it's not rendering and the fields are staying empty:
I expect seeing in the list myself (my details) and later on if I'm connecting to the app from Incognito to update the connected users list. But right now as you can see, I don't see my info.
I'm putting only the relevent parts of my code:
user component ts-
export class UsersComponent implements OnInit {
private signalrConnectionUrl='https://localhost:7014/userHub';
private addClientUrl='https://localhost:7014/Contacts/AddChatClient';
private sendMessageToAllUrl='https://localhost:7014/Contacts/SendMessageToAll';
chatClientId= getClientId();
chatMessage: string="";
userName=getUserName();
users: ConnectedClientModel[]=[];
constructor(public signalRService: SignalrService, private userService: UsersService, private http: HttpClient) { }
ngOnInit(): void {
this.startSignalrConnection();
};
startSignalrConnection(): void {
this.signalRService.startSignalrConnection(this.signalrConnectionUrl)
.then((signalrHubConnectionId) => {
firstValueFrom(this.http.post(this.addClientUrl,
buildNewChatClientConnectionModel(this.chatClientId, signalrHubConnectionId, this.userName!)))
.then((response) => {
this.signalRService.addListeners();
//getting all connected users
this.userService.getConnectedUsers().subscribe((users)=>{
this.users=users;
console.log(this.users);
})
console.log("Signalr started successfully with connectionId: " + signalrHubConnectionId + " And ready to get messages");
})
.catch((error) => {
console.log("Error while adding new chat client:", error);
alert("Error while adding new chat client:" + error);
console.log("chatClientId: " + this.chatClientId);
console.log("signalrHubConnectionId: " + signalrHubConnectionId);
});
})
.catch((error) => {
console.log("Error while establishing signalr connection:", error);
alert("Error while establishing signalr connection:" + error);
});
}
user component html (only the user list part)-
<h4 class="mb-3">List of Users</h4>
<div *ngFor="let user of users">
<div class="mb-2 mt-2">
<div><strong>Name</strong>{{user.Name}}</div>
<div><strong>Id</strong>{{user.ChatClientId}}</div>
<hr>
</div>
</div>
user service-
export class UsersService {
private getConnectedUsersUrl='https://localhost:7014/Contacts/GetConnectedUsers';
constructor(private http: HttpClient) { }
getConnectedUsers(): Observable<ConnectedClientModel[]> {
return this.http.get<ConnectedClientModel[]>(this.getConnectedUsersUrl);
}
}
ConnectedClientModel-
import { Guid } from "guid-typescript"
export interface ConnectedClientModel {
ChatClientId: Guid,
ConnectionId: string,
Name: string
}
Server get request-
[HttpGet("GetConnectedUsers")]
public async Task<IActionResult> GetConnectedUsers()
{
var allConnectedUsers = _signalrService.GetAllConnectedUsers();
return Ok(allConnectedUsers.Entity);
}
The properties coming from your backend start with a lowercase letter, yet in your Angular-model their first letter is capitalized. As a consequence the mapping might not be working anymore.
Can you once try to modify your model as follows?:
import { Guid } from "guid-typescript"
export interface ConnectedClientModel {
chatClientId: Guid,
connectionId: string,
name: string
}
And then also adapt the following two lines of your html:
<div><strong>Name</strong>{{user.name}}</div>
<div><strong>Id</strong>{{user.chatClientId}}</div>
I tried to use SignalR stream to send real-time data to client and then display the data on Angular component's template.
Here is my code snippet, this function is bound to a button (click) event in the template:
getMessage(message: string) {
this.connection = new HubConnectionBuilder().withUrl('/messageHub').build();
const connect = this.connection;
connect.start().then(function () {
connect.stream('SendMessage', message).subscribe({
next: (item) => {
console.log(item); // <= this works: I can get the data from server
let li = document.createElement("li"); // <= works
li.textContent = item; // <= works
document.getElementById("ulId").appendChild(li); // <= does not work, cannot get dom element by Id....
},
complete: () => {
console.log("finished.");
},
error: (err) => {
console.log(err);
}
});
})
}
I can't make it to work and will appreciate it if someone can provide me a working example.
Thanks,
Jack
index.html Code be like this :
<div class="container">
<input type="button" id="startStreaming" value="Send" />
<ul id="discussion"></ul>
</div>
.JS code be like this :
var connection = new signalR.HubConnectionBuilder()
.withUrl("/streaming")
.build();
var button = document.getElementById("startStreaming");
function startStreaming(){
connection.stream("StartStreaming").subscribe({
next: onStreamReceived,
err: function(err){
console.log(err);
},
complete: function(){
console.log("finished streaming");
}
});
}
connection.on("streamStarted", function(){
startStreaming();
});
button.addEventListener("click", event => {
connection.invoke("sendStreamInit");
});
function onStreamReceived(data){
console.log("received: " + data);
var liElement = document.createElement('li');
liElement.innerHTML = '<strong>' + "received" + '</strong>: ' + data;
document.getElementById('discussion').appendChild(liElement);
}
connection.start();
You might be looking for this :
https://radu-matei.com/blog/signalr-core/#streaming
https://github.com/radu-matei/signalr-samples/blob/master/streaming/web/wwwroot/index.html
Add a property to your class messages: string[]; . Update your click event
getMessage(message: string) {
this.connection = new HubConnectionBuilder().withUrl('/messageHub').build();
const connect = this.connection;
connect.start().then(function () {
connect.stream('SendMessage', message).subscribe({
next: (item) => {
this.messages.push(item);
},
complete: () => {
console.log("finished.");
},
error: (err) => {
console.log(err);
}
});
})
}
Now set your html something like this
<ul class="heroes">
<li *ngFor="let message of messages">
</li>
</ul>
Now on wards for each signalR iteration your new data will be pushed into messages array and it will update in html as well.
I've been trying to run several procedural code on server with parameter that i put on form and execute with MVC Controller. I want every step (method / function) on controller that have been called will update information to the client Web real-time.
I've been trying to use SignalR to update realtime push notification / information to client, its work with client trigger but when i trying to call hub from controller it doest work.
here is my Controller Code :
[HttpPost]
public string data (Models.ExModel data)
{
var hub = GlobalHost.ConnectionManager.GetHubContext<Hubs.MyHub1>();
//Execute Code
//send to client
hub.Clients.All.Message(data.apaAja);
return "success";
}
here is my client code :
<h2>Index</h2>
#Ajax.BeginForm("data", "Home", FormMethod.Post, null) {
<div class="input-group">
<span>Apa Aja</span>
#Html.EditorFor(model => model.apaAja, new { htmlhtmlAttributes = new { #id = "apaAja" } })
</div>
<div class="input-group">
<span> Boleh </span>
#Html.EditorFor(model => model.boleh, new { htmlhtmlAttributes = new { #id = "boleh" } })
</div>
<button id="subm" type="submit">Submit</button>
<div id="container">
</div>
#section scripts{
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<script src="~/Scripts/jquery.signalR-2.2.1.min.js"></script>
<script src="~/signalr/hubs"></script>
<script>
$(document).ready(function () {
var c = $.connection.myHub1;
c.client.messageSend = function (message) {
var encodedMsg = $('<div />').text(message).html();
// Add the message to the page.
$('#container').append('<li> < strong >' + 'Info Message : ' +
'</strong >: ' + encodedMsg + '</li >');
};
$.connection.hub.start();
});
</script>
and here is my Hub Class :
namespace SignalR1.Hubs
{
public class MyHub1 : Hub
{
public void Message(string message)
{
Clients.All.messageSend(message);
}
}
}
You have a client-side callback called messageSend, then in your hub you rightly have Clients.All.messageSend, however when you use the GlobalHost.ConnectionManager.GetHubContext you are accessing the hub context not the hub class itself.
So change it to:
var hub = GlobalHost.ConnectionManager.GetHubContext<Hubs.MyHub1>();
//you don't actually have access to the MyHub1 class at this point
// instead of
// hub.Clients.All.Message(data.apaAja);
// you need
hub.Clients.All.messageSend(data.apaAja);
In fact, the hub class method becomes slightly redundant when using this mechanism. I normally use the hub class for managing connections and clients using the overrides for onconnected etc...
I want to call a javascript and a function written in Model Class using a single button click. I used the following code:
<script language="javascript" type="text/javascript">
function RunEXE() {
var txtfile = document.getElementById("txtFileName");
//var txtProgram = document.getElementById("txtProgram");
//if ((!String.IsNullOrEmpty(txtfile)) && (!String.IsNullOrWhiteSpace(txtProgram))) {
if (txtfile.value != "") {
var oShell = new ActiveXObject("WScript.Shell");
//var prog = "c:\\Pgms\\sample0.exe";
var prog = "\\\\Test-PC\\Programms\\" + txtfile.value + ".exe";
oShell.Run('"' + prog + '"', 1);
} else {
alert('The file name must be entered in file name textbox');
}
}
</script>
<input type="submit" name="button" value="Run" onclick="RunEXE()" />
The below code is Model function:
public ActionResult Run(UserProgram userProgram)
{
SaveAndCompile(userProgram);
return null;
}
But its working with Run() alone and not running RunEXE()
[HttpPost]
public ActionResult RunAction(string option1)
{
//if needed, you can use the "option1" value to determine the UserProgram to pass
UserProgram userProgram = new UserProgram();
Run(userProgram);
//you can return a JSON reuslt that you can evaluate back at the client
return Json(new { #Success = true, #MyString = "a string" });
}
$.post('#Url.Action("RunAction", "MyController")',
{
option1: "some optional value"
},
function (data) {
alert("success!");
//here you have access to your JSON result via data, for example:
//data.Success = true
//data.MyString = "a string"
}
);
In your case, you can submit your form by JQuery submit function.
I assume your code will like below:
<form id="form" action="/Run">
// your some inputs
<input type="submit" name="button" value="Run" />
</form>
And the javascript for submitting will be:
$(function() {
$('#form').submit(function() {
// to do something before the form is submitted
RunEXE();
return true; // return false to cancel form action
});
});
Cheers.
I am working on an ASP.NET MVC 4 Application that imports and processes a CSV file. I am using a standard form and controller for the upload. Here is an overview of what I am doing currently:
Controller Logic
public ActionResult ImportRecords(HttpPostedFileBase importFile){
var fp = Path.Combine(HttpContext.Server.MapPath("~/ImportUploads"), Path.GetFileName(uploadFile.FileName));
uploadFile.SaveAs(fp);
var fileIn = new FileInfo(fp);
var reader = fileIn.OpenText();
var tfp = new TextFieldParser(reader) {TextFieldType = FieldType.Delimited, Delimiters = new[] {","}};
while(!tfp.EndOfData){
//Parse records into domain object and save to database
}
...
}
HTML
#using (Html.BeginForm("ImportRecords", "Import", FormMethod.Post, new { #id = "upldFrm", #enctype = "multipart/form-data" }))
{
<input id="uploadFile" name="uploadFile" type="file" />
<input id="subButton" type="submit" value="UploadFile" title="Upload File" />
}
The import file can contain a large number of records (average 40K+) and can take quite some time to complete. I'd rather not have a user sitting at the import screen for 5+ minutes for each file processed. I have considered adding a console application to watch the uploads folder for new files, and process when something new is added, but would like to see what input I receive from the community before starting my journey down this path.
Is there a more efficient way to handle this operation?
Is there a way to perform this action, allowing the user to continue about his/her merry way, and then notify the user when processing is done?
The solution to the issue I was having is a bit complex, but works similar to the IFrame fix. The result is a pop-up window that handles the processing, allowing the user to continue navigation throughout the site.
The file is submitted to the server (UploadCSV controller), a Success page is returned with a bit of JavaScript to handle the initial kick-off of the processing. When the user clicks "Begin Processing", a new window is opened (ImportProcessing/Index) that loads the initial status (kicking off an interval loop that retrieves status updates) and then makes a call to the "StartProcessing" action, kicking off the processing process.
The "FileProcessor" class that I am using is housed in a static dictionairy variable within the ImportProcessing controller; allowing for status results based on the key. The FileProcessor is promptly removed after the operation is complete or an error is encountered.
Upload Controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UploadCSV(HttpPostedFileBase uploadFile)
{
var filePath = string.Empty;
if (uploadFile.ContentLength <= 0)
{
return View();
}
filePath = Path.Combine(Server.MapPath(this.UploadPath), "DeptartmentName",Path.GetFileName(uploadFile.FileName));
if (new FileInfo(filePath).Exists)
{
ViewBag.ErrorMessage =
"The file currently exists on the server. Please rename the file you are trying to upload, delete the file from the server," +
"or contact IT if you are unsure of what to do.";
return View();
}
else
{
uploadFile.SaveAs(filePath);
return RedirectToAction("UploadSuccess", new {fileName = uploadFile.FileName, processType = "sonar"});
}
}
[HttpGet]
public ActionResult UploadSuccess(string fileName, string processType)
{
ViewBag.FileName = fileName;
ViewBag.PType = processType;
return View();
}
Upload Success HTML:
#{
ViewBag.Title = "UploadSuccess";
}
<h2>File was uploaded successfully</h2>
<p>Your file was uploaded to the server and is now ready to be processed. To begin processing this file, click the "Process File" button below.
</p>
<button id="beginProcess" >Process File</button>
<script type="text/javascript">
$(function () {
$("#beginProcess").click(BeginProcess);
function BeginProcess() {
window.open("/SomeController/ImportProcessing/Index?fileName=#ViewBag.FileName&type=#ViewBag.PType", "ProcessStatusWin", "width=400, height=250, status=0, toolbar=0, scrollbars=0, resizable=0");
window.location = "/Department/Import/Index";
}
});
</script>
Once this new window is opened up, the file processing begins. Updates are retrieved from a custom FileProcessing class.
ImportProcessing Controller:
public ActionResult Index(string fileName, string type)
{
ViewBag.File = fileName;
ViewBag.PType = type;
switch (type)
{
case "somematch":
if (!_fileProcessors.ContainsKey(fileName)) _fileProcessors.Add(fileName, new SonarCsvProcessor(Path.Combine(Server.MapPath(this.UploadPath), "DepartmentName", fileName), true));
break;
default:
break;
}
return PartialView();
}
ImportProcessing Index:
#{
ViewBag.Title = "File Processing Status";
}
#Scripts.Render("~/Scripts/jquery-1.8.2.js")
<div id="StatusWrapper">
<div id="statusWrap"></div>
</div>
<script type="text/javascript">
$(function () {
$.ajax({
url: "GetStatusPage",
data: { fileName: "#ViewBag.File" },
type: "GET",
success: StartStatusProcess,
error: function () {
$("#statusWrap").html("<h3>Unable to load status checker</h3>");
}
});
function StartStatusProcess(result) {
$("#statusWrap").html(result);
$.ajax({
url: "StartProcessing",
data: { fileName: "#ViewBag.File" },
type: "GET",
success: function (data) {
var messag = 'Processing complete!\n Added ' + data.CurrentRecord + ' of ' + data.TotalRecords + " records in " + data.ElapsedTime + " seconds";
$("#statusWrap #message").html(messag);
$("#statusWrap #progressBar").attr({ value: 100, max: 100 });
setTimeout(function () {
window.close();
}, 5000);
},
error: function (xhr, status) {
alert("Error processing file");
}
});
}
});
</script>
Finally the Status Checker html:
#{
ViewBag.Title = "GetStatusPage";
}
<h2>Current Processing Status</h2>
<h5>Processing: #ViewBag.File</h5>
<h5>Updated: <span id="processUpdated"></span></h5>
<span id="message"></span>
<br />
<progress id="progressBar"></progress>
<script type="text/javascript">
$(function () {
var checker = undefined;
GetStatus();
function GetStatus() {
if (checker == undefined) {
checker = setInterval(GetStatus, 3000);
}
$.ajax({
url: "GetStatus?fileName=#ViewBag.File",
type: "GET",
success: function (result) {
result = result || {
Available: false,
Status: {
TotalRecords: -1,
CurrentRecord: -1,
ElapsedTime: -1,
Message: "No status data returned"
}
};
if (result.Available == true) {
$("#progressBar").attr({ max: result.Status.TotalRecords, value: result.Status.CurrentRecord });
$("#processUpdated").text(result.Status.Updated);
$("#message").text(result.Status.Message);
} else {
clearInterval(checker);
}
},
error: function () {
$("#statusWrap").html("<h3>Unable to load status checker</h3>");
clearInterval(checker);
}
});
}
});
</script>
Just a thought but you could thread the processing of your CSV files and on completion of that task call another method that basically provides a modal dialog or some kind of javascript alert on the client side letting the user know that the processing has completed.
Task.Factory.StartNew(() => ProcessCsvFile(fp)).ContinueWith((x) => NotifyUser());
or something along those lines. I think that ultimately you are gonna wanna look at some kind of threading because it certainly does not make sense for a user to be stuck looking at a screen while some kind of server side processing takes place.