У меня проблема с предположительно нетрадиционным вариантом использования Razor и. NET Core 3.1. У меня есть страница Razor с привязками моделей для моих полей, когда пользователь нажимает кнопку, он перенаправляет ее в функцию NonAction для выполнения некоторых потенциально длительных (определяемых пользователем промежуток времени) вычислений. Я хочу отправить сообщение в текстовое поле, когда любая из 3 параллельно выполняемых задач отправляет сообщение, но я не могу обновить текстовое поле на стороне клиента с помощью привязки модели, и я не хочу вызывать обратную передачу каждый раз, когда состояние модели обновления (поскольку параллельные задачи могут передавать сообщения от 100 мс до 5000 мс).
Я, вероятно, делаю это неправильно, поскольку я довольно новичок в Razor. Идея состоит в том, что параллельные задачи используют мой пользовательский ConcurrentQueue (чтобы я мог добавить обработчик событий, когда сообщение ставится в очередь), и всякий раз, когда событие в очереди запускает основной поток, обновляет текстовое поле.
Вот .cs html:
@page
@using AMQSimNetCore.Pages;
@model AMQSimNetCore.Pages.Producer.ProducerModel
@{
ViewData["Title"] = "Producer";
}
<h1>Producer</h1>
<div class="jumbotron">
<h1>AMQ Producer</h1>
@using (Html.BeginForm("OnPostRunSimulator_OnClickAsync", "Producer", FormMethod.Post))
{
<div class="container" id="dv_ctrlsContainer" style="border-color:grey; border-style:solid; border-width:thin; margin-bottom:10px">
<div class="row">
<div class="col-md-3">
<input type="checkbox" asp-for="TalkerSelected" />
<label ID="lbl_talker">Generate Talker Messages</label>
</div>
<div class="col-md-5">
<label ID="lbl_talkerTime">Time (in ms) between generated talker messages.</label>
</div>
<div class="col-md-3">
<input type="text" asp-for="TalkerTime" width="75" />
</div>
</div>
<div class="row">
<div class="col-md-3">
<input type="checkbox" asp-for="PartInfoSelected" />
<label ID="lbl_partInfo">Generate PartInfo Messages</label>
</div>
<div class="col-md-5">
<label ID="lbl_partInfoTime">Time (in ms) between generated part info messages.</label>
</div>
<div class="col-md-3">
<input type="text" asp-for="PartInfoTime" Width="75" />
</div>
</div>
<div class="row">
<div class="col-md-3">
<input type="checkbox" asp-for="ConfInfoSelected" />
<label ID="lbl_confInfo">Generate ConfInfo Messages</label>
</div>
<div class="col-md-5">
<label ID="lbl_confInfoTime">Time (in ms) between generated conf info messages.</label>
</div>
<div class="col-md-3">
<input type="text" asp-for="ConfInfoTime" Width="75" />
</div>
</div>
<div class="row">
<div class="col-md-3">
<label ID="lbl_totalSimTime">Total Time (in ms) to run Simulator</label>
</div>
<div class="col-md-2">
<input type="text" asp-for="TotalConfTime" Width="75" />
</div>
</div>
<div class="row">
<div class="col-md-3">
<label ID="lbl_numSimultaneousConfs">Number of Simulatenous Conferences</label>
</div>
<div class="col-md-2">
<input type="text" asp-for="NumSimultaneousConfs" Width="75" />
</div>
</div>
<div class="row">
<div class="col-md-3">
<label ID="lbl_numParts">Number of participants.</label>
</div>
<div class="col-md-2">
<input type="text" asp-for="NumParts" Width="75" />
</div>
</div>
<div class="row">
<div class="col-md-3">
<label ID="lbl_endpoint">Choose Endpoint</label>
</div>
<div class="col-md-5">
<select asp-for="EndpointString" class="form-control">
<option>xxxxxxxx</option>
<option>xxxxxxxx</option>
<option>xxxxxxxx</option>
<option>xxxxxxxx</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-3">
<label ID="lbl_userName">Username</label>
</div>
<div class="col-md-5">
<input type="text" asp-for="UserName" Width="150" />
</div>
</div>
<div class="row">
<div class="col-md-3">
<label ID="lbl_password">Password</label>
</div>
<div class="col-md-5">
<input type="text" asp-for="Password" Width="150" />
</div>
</div>
</div>
<div class="container" style="border-color:grey; border-style:solid; border-width:thin; margin-bottom:10px">
<div class="row">
<div class="col-md-12">
<input type="text"style="height:auto; width:100% !important;" asp-for="Logger" />
</div>
</div>
</div>
<div>
<input type="submit" ID="btn_run" asp-page-handler="RunSimulator_OnClick" Width="125" value="Run Simulator" />
</div>
}
</div>
Вот код .cs html .cs:
using Apache.NMS;
using Apache.NMS.ActiveMQ;
using Apache.NMS.ActiveMQ.Commands;
using Common.Logging;
using ServiceStack.Text;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web.UI;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System.ComponentModel.DataAnnotations;
using ISession = Apache.NMS.ISession;
using static AMQSimNetCore.ConcurrentQueueWithEvents<string>;
namespace AMQSimNetCore.Pages.Producer
{
[BindProperties]
public class ProducerModel : PageModel
{
public string EndpointString { get; set; }
public bool TalkerSelected { get; set; } = true;
public int TalkerTime { get; set; } = 1000;
public bool PartInfoSelected { get; set; } = true;
public int PartInfoTime { get; set; } = 1000;
public bool ConfInfoSelected { get; set; } = true;
public int ConfInfoTime { get; set; } = 1000;
public int TotalConfTime { get; set; } = 60000;
public int NumSimultaneousConfs { get; set; } = 5;
public int NumParts { get; set; } = 2;
public string UserName { get; set; } = "xxxxxx";
public string Password { get; set; } = "xxxxxx";
public string Logger { get; set; }
private readonly ConcurrentQueueWithEvents<string> loggerText = new ConcurrentQueueWithEvents<string>();
private ConnectionFactory factory;
private IConnection connection;
private IMessageProducer producer;
private ISession sessionProducer;
private int msgNum = 0, loopTimes = 0;
private List<int> ConfIds = new List<int>();
private readonly string publishTopicName = "xxxxxxx";
public void OnGet()
{
ConfIds = new List<int>(Enumerable.Range(1, NumSimultaneousConfs));
HttpContext.Session.SetString("ConfIds", JsonSerializer.SerializeToString(ConfIds));
}
[NonAction]
public async void OnPostRunSimulator_OnClickAsync(IFormCollection collection)
{
loggerText.OnEnqueuedMessage += MessageEnqueued;
bool _config = await LoadConfig(collection);
if (!_config)
{
this.Response.Redirect("/Error");
}
if(EndpointString.Contains("xxxxxxx"))
{
this.factory = new ConnectionFactory("ssl://" + EndpointString + "?wireFormat.maxInactivityDuration=30000");
}
else
{
this.factory = new ConnectionFactory("failover:(ssl://" + EndpointString + "?wireFormat.maxInactivityDuration=30000)");
}
this.factory.AsyncSend = true;
this.connection = factory.CreateConnection(UserName, Password);
((Connection)this.connection).AsyncSend = true;
this.connection.Start();
this.sessionProducer = this.connection.CreateSession();
Task pt = Task.Run(() => {
Parallel.Invoke(
() =>
{
CalcLoop();
},
() =>
{
if (ConfInfoSelected)
{
GenerateConfInfo(ConfIds);
}
},
() =>
{
if (PartInfoSelected)
{
GeneratePartInfo(ConfIds);
}
},
() =>
{
if (TalkerSelected)
{
GenerateTalker(ConfIds);
}
}
);
});
ModelState.Clear();
Task.WhenAll(pt);
}
void MessageEnqueued()
{
if (loggerText.TryDequeue(out string _msg))
{
Logger += _msg + System.Environment.NewLine;
}
}
private Task<bool> CalcLoop()
{
bool _finished = false;
while (loopTimes < (this.TotalConfTime / 1000))
{
Thread.Sleep(1000);
loopTimes++;
}
return Task.FromResult(_finished);
}
private Task<bool> GenerateConfInfo(List<int> confIds)
{
bool _finished = false;
while (loopTimes < (this.TotalConfTime / 1000))
{
foreach (int confId in confIds)
{
var conf = new ConfInfo()
{
ConfId = confId.ToString(),
Active = true,
TalkerNotify = true
};
using (var m = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(m, conf);
ActiveMQBytesMessage msg = new ActiveMQBytesMessage();
msg.Content = m.ToArray();
msg.Properties.SetString("confid", confId.ToString());
Interlocked.Increment(ref msgNum);
msg.Properties.SetString("msgNum", msgNum.ToString());
msg.NMSDeliveryMode = MsgDeliveryMode.NonPersistent;
msg.NMSType = "confinfo";
msg.NMSTimeToLive = new TimeSpan(0, 0, 10);
msg.ReadOnlyBody = true;
var topic = new ActiveMQTopic(publishTopicName + "." + confId.ToString());
this.producer = this.sessionProducer.CreateProducer(topic);
this.producer.Send(msg);
loggerText.Enqueue(String.Format("{0} - MsgNum {1}: Sending ConfInfo message for ConfId {2}", DateTime.UtcNow.ToString(), msgNum.ToString(), confId.ToString()));
}
Thread.Sleep(ConfInfoTime);
}
}
return Task.FromResult(_finished);
}
private Task<bool> GeneratePartInfo(List<int> confIds)
{
bool _finished = true;
while (loopTimes < (this.TotalConfTime / 1000))
{
foreach (int confId in confIds)
{
Random partId = new Random();
var part = new PartInfo()
{
ConfId = confId.ToString(),
Connected = true,
PartId = partId.Next().ToString()
};
using (var m = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(m, part);
ActiveMQBytesMessage msg = new ActiveMQBytesMessage();
msg.Content = m.ToArray();
msg.Properties.SetString("confid", confId.ToString());
Interlocked.Increment(ref msgNum);
msg.Properties.SetString("msgNum", msgNum.ToString());
msg.NMSDeliveryMode = MsgDeliveryMode.NonPersistent;
msg.NMSType = "partinfo";
msg.NMSTimeToLive = new TimeSpan(0, 0, 10);
msg.ReadOnlyBody = true;
var topic = new ActiveMQTopic(publishTopicName + "." + confId.ToString());
this.producer = this.sessionProducer.CreateProducer(topic);
this.producer.Send(msg);
loggerText.Enqueue(String.Format("{0} - MsgNum {1}: Sending PartInfo message for ConfId {2}, PartId {3}", DateTime.UtcNow.ToString(), msgNum.ToString(), confId.ToString(), part.PartId));
}
Thread.Sleep(PartInfoTime);
}
}
return Task.FromResult(_finished);
}
private Task<bool> GenerateTalker(List<int> confIds)
{
bool _finished = true;
while (loopTimes < (this.TotalConfTime / 1000))
{
foreach (int confId in confIds)
{
var talker = new Talker()
{
ConfId = confId.ToString(),
PartIds = new List<string> { "xxxxxxx" }
};
using (var m = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(m, talker);
ActiveMQBytesMessage msg = new ActiveMQBytesMessage();
msg.Content = m.ToArray();
msg.Properties.SetString("confid", confId.ToString());
Interlocked.Increment(ref msgNum);
msg.Properties.SetString("msgNum", msgNum.ToString());
msg.NMSDeliveryMode = MsgDeliveryMode.NonPersistent;
msg.NMSType = "talker";
msg.NMSTimeToLive = new TimeSpan(0, 0, 10);
msg.ReadOnlyBody = true;
var topic = new ActiveMQTopic(publishTopicName + "." + confId.ToString());
this.producer = this.sessionProducer.CreateProducer(topic);
this.producer.Send(msg);
loggerText.Enqueue(String.Format("{0} - MsgNum {1}: Sending Talker message for ConfId {2}", DateTime.UtcNow.ToString(), msgNum.ToString(), confId.ToString()));
}
Thread.Sleep(TalkerTime);
}
}
return Task.FromResult(_finished);
}
private Task<bool> LoadConfig(IFormCollection collection)
{
bool _success = false;
try
{
if (collection.Keys.Contains("EndpointString"))
{
this.EndpointString = collection["EndpointString"];
}
if (collection.Keys.Contains("TalkerSelected"))
{
this.TalkerSelected = bool.Parse(collection["TalkerSelected"].ToArray()[0]);
}
if (collection.Keys.Contains("TalkerTime"))
{
this.TalkerTime = Int32.Parse(collection["TalkerTime"]);
}
if (collection.Keys.Contains("PartInfoSelected"))
{
this.PartInfoSelected = bool.Parse(collection["PartInfoSelected"].ToArray()[0]);
}
if (collection.Keys.Contains("PartInfoTime"))
{
this.PartInfoTime = Int32.Parse(collection["PartInfoTime"]);
}
if (collection.Keys.Contains("ConfInfoSelected"))
{
this.ConfInfoSelected = bool.Parse(collection["ConfInfoSelected"].ToArray()[0]);
}
if (collection.Keys.Contains("ConfInfoTime"))
{
this.ConfInfoTime = Int32.Parse(collection["ConfInfoTime"]);
}
if (collection.Keys.Contains("TotalConfTime"))
{
this.TotalConfTime = Int32.Parse(collection["TotalConfTime"]);
}
if (collection.Keys.Contains("NumSimultaneousConfs"))
{
this.NumSimultaneousConfs = Int32.Parse(collection["NumSimultaneousConfs"]);
ConfIds = new List<int>(Enumerable.Range(1, NumSimultaneousConfs));
HttpContext.Session.SetString("ConfIds", JsonSerializer.SerializeToString(ConfIds));
}
if (collection.Keys.Contains("NumParts"))
{
this.NumParts = Int32.Parse(collection["NumParts"]);
}
if (collection.Keys.Contains("UserName"))
{
this.UserName = collection["UserName"];
}
if (collection.Keys.Contains("Password"))
{
this.Password = collection["Password"];
}
if (collection.Keys.Contains("Logger"))
{
this.Logger = collection["Logger"];
}
_success = true;
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
return Task.FromResult(_success);
}
}
Любая помощь будет приветствоваться (даже если это сказать мне, что я идиот)!