如何在ASP MVC中实现“用户交互”依赖注入?

时间:2022-09-02 09:40:11

I have a full engine that relies on abstractions based on user interactions. This works great with WPF/Xamarin app, cause I can implements this abstractions with window/form.

我有一个完全依赖于基于用户交互的抽象的引擎。这对于WPF/Xamarin应用程序非常有用,因为我可以通过窗口/表单实现这个抽象。

I have a little problem for porting this engine into ASP MVC.

我有一个将这个引擎移植到ASP MVC的问题。

A simple example can be show as this.

一个简单的例子可以如下所示。

Abstraction interface (simplified)

抽象接口(简化)

public interface IQuestionBox
{
    Task<bool> ShowYesNoQuestionBox(string message);
}

For WPF, it's really simple, I implement this interface as return the result of a window by calling ShowDialog().

对于WPF,它非常简单,我通过调用ShowDialog()实现这个接口,返回窗口的结果。

In a simple business class, I can have this kind of calls (simplified) :

在一个简单的商务类中,我可以有这种调用(简化):

public async Task<string> GetValue(IQuestionBox qbox)
{
    if(await qbox.ShowYesNoQuestionBox("Question ?"))
    {
        return "Ok";
    }
    return "NOk";
}

I really don't see how can I implement this kind of behavior in ASP, due to stateless of HTTP, knowing that this kind of call can be as various as domain/business need. The way I think it should be done is by returning a PartialView to inject into popup, but I don't see how to do this without breaking all the process ...

由于HTTP是无状态的,我真的不知道如何在ASP中实现这种行为,因为我知道这种调用可以根据域/业务需求的不同而不同。我认为应该这样做的方法是返回一个PartialView来注入到popup中,但是我不知道如何在不破坏所有过程的情况下完成这个操作……

Anyone has ever done this ?

有人做过这个吗?

4 个解决方案

#1


1  

You can create a web socket connection from client side to server side. And work with front-end content with web socket request. It could be implemented as following:

您可以创建从客户端到服务器端的web套接字连接。使用web socket请求处理前端内容。它可以实现如下:

Client side:

客户端:

$app = {
    uiEventsSocket : null,
    initUIEventsConnection : function(url) {
        //create a web socket connection
        if (typeof (WebSocket) !== 'undefined') {
                this.uiEventsSocket = new WebSocket(url);
            } else if (typeof (MozWebSocket) !== 'undefined') {
                this.uiEventsSocket = new MozWebSocket(url);
            } else {
                console.error('WebSockets unavailable.');
            }

            //notify if there is an web socket error
            this.uiEventsSocket.onerror = function () {
                console.error('WebSocket raised error.');
            }

            this.uiEventsSocket.onopen = function () {
                console.log("Connection to " + url + " established");
            }

            //handling message from server side
            this.uiEventsSocket.onmessage = function (msg) {
                this._handleMessage(msg.data);
            };
    },

    _handleMessage : function(data){
        //the message should be in json format
        //the next line fails if it is not
        var command = JSON.parse(data);

        //here is handling the request to show prompt
        if (command.CommandType == 'yesNo') {
            var message = command.Message;

            var result = confirm(message);
            //not sure that bool value will be successfully converted
            this.uiEventsSocket.send(result ? "true" : "false");
        }
    }
}

And init it from ready or load event:

从就绪或加载事件初始化:

window.onload = function() { $app.initUIEventsConnection(yourUrl); }

Note that you url should begin with ws:// instead of http:// and wss:// instead of https:// (Web Sockets and Web Sockets Secure).

注意,url应该从ws开始://而不是http://和wss://而不是https:// (Web Sockets和Web Sockets Secure)。

Server side.

服务器端。

Here is a good article for how to setup web sockets at asp.net core application or you could find another one. Note that you should group web socket connections from single user and if you want to send a message to the concrete user, you should send message for every connection from this user.

这里有一篇很好的文章,介绍如何在asp.net核心应用程序中设置web sockets,或者您可以找到另一个。注意,您应该将web socket连接从单个用户分组,如果您想向具体的用户发送消息,您应该向该用户的每个连接发送消息。

Every web socket you should accept with AcceptWebSocketAsync() method call and then add instance of this web socket to singleton, which contains a set of web sockets connection groupped by user.

您应该使用AcceptWebSocketAsync()方法调用接受的每个web套接字,然后将这个web套接字的实例添加到singleton中,singleton包含一组由用户分组的web套接字连接。

The following class will be used to operate commands:

下面的类将用于操作命令:

public class UICommand
{
    public string CommandType { get; set; }
    public string Message { get; set; }
    public Type ReturnType { get; set; }
}

And a full code of singleton for handling sockets

以及用于处理套接字的单例的完整代码

public class WebSocketsSingleton
{
    private static WebSocketsSingleton _instance = null;
    //here stored web sockets groupped by user
    //you could use user Id or another marker to exactly determine the user
    private Dictionary<string, List<WebSocket>> _connectedSockets;

    //for a thread-safety usage
    private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();

    public static WebSocketsSingleton Instance {
        get {
            if (this._instance == null)
            {
                this._instance = new WebSocketsSingleton();
            }

            return this._instance;
        }
    }

    private WebSocketsSingleton()
    {
        this._connectedSockets = new Dictionary<string, List<WebSocket>>();
    }

    /// <summary>
    /// Adds a socket into the required collection
    /// </summary>
    public void AddSocket(string userName, WebSocket ws)
    {
        if (!this._connectedSockets.ContainsKey(userName))
        {
            Locker.EnterWriteLock();
            try
            {
                this._connectedSockets.Add(userName, new List<WebSocket>());
            }
            finally
            {
                Locker.ExitWriteLock();
            }
        }

        Locker.EnterWriteLock();
        try
        {
            this._connectedSockets[userName].Add(ws);
        }
        finally
        {
            Locker.ExitWriteLock();
        }
    }

    /// <summary>
    /// Sends a UI command to required user
    /// </summary>  
    public async Task<string> SendAsync(string userName, UICommand command)
    {
        if (this._connectedSockets.ContainsKey(userName))
        {
            var sendData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(command));

        foreach(var item in this._connectedSockets[userName])
        {
            try
            {
                await item.SendAsync(new ArraySegment<byte>(sendData), WebSocketMessageType.Text, true, CancellationToken.None);
            }
            catch (ObjectDisposedException)
            {
                //socket removed from front end side
            }
        }

            var buffer = new ArraySegment<byte>(new byte[1024]);
            var token = CancellationToken.None;         
            foreach(var item in this._connectedSockets[userName])
            {
                await Task.Run(async () => {
                    var tempResult = await item.ReceiveAsync(buffer, token);
                    //result received
                    token = new CancellationToken(true);
                });
            }

            var resultStr = Encoding.Utf8.GetString(buffer.Array);

            if (command.ReturnType == typeof(bool))
            {
                return resultStr.ToLower() == "true";
            }

            //other methods to convert result into required type

            return resultStr;
        }

        return null;
    }
}

Explanation:

解释:

  • on establishing connection from web socket it will be added with AddSocket method
  • 在从web套接字建立连接时,将使用AddSocket方法进行添加
  • on sending request to show a message, the required command will be passed into SendAsync method
  • 在发送显示消息的请求时,所需的命令将被传递到SendAsync方法中
  • the command will be serialized to JSON (using Json.Net, however you could serialize in your way) and send to all sockets, related to the required user
  • 该命令将被序列化为JSON(使用JSON)。Net,但是您可以以自己的方式序列化)并发送到与所需用户相关的所有套接字
  • after the command sent, application will wait for respond from front end side
  • 发送命令后,应用程序将等待前端的响应
  • the result will be converted to required type and sent back to your IQuestionBox
  • 结果将被转换为所需的类型并发送回您的IQuestionBox

In the web socket handling your should add some kind of the following code:

在处理web套接字时,您应该添加以下代码:

app.Use(async (http, next) =>
{
    if (http.WebSockets.IsWebSocketRequest)
    {
        var webSocket = await http.WebSockets.AcceptWebSocketAsync();
        var userName = HttpContext.Current.User.Identity.Name;
        WebSocketsSingleton.Instance.AddSocket(userName, webSocket);

        while(webSocket.State == WebSocketState.Open)
        {
            //waiting till it is not closed         
        }

        //removing this web socket from the collection
    }
});

And your method implementation of ShowYesNoQuestionBox should be kind of following:

你的ShowYesNoQuestionBox的方法实现应该是这样的:

public async Task<bool> ShowYesNoQuestionBox(string userName, string text)
{
    var command = new UICommand
    {
        CommandType = "yesNo",
        Message = text,
        ReturnType = typeof(bool)
    };

    return await WebSocketsSingleton.Instance.SendAsync(string userName, command);
}

Note that there should be added userName to prevent sending the same message to all of the connected users.

注意,应该添加用户名,以防止向所有连接的用户发送相同的消息。

WebSocket should create the persistent connection between server and client sides, so you could simply send commands in two ways.

WebSocket应该在服务器端和客户端之间创建持久连接,因此您可以用两种方式发送命令。

I am kindly new to Asp.Net Core, so the final implementation could be a bit different from this.

我对Asp很有兴趣。Net Core,所以最终实现可能与这个有所不同。

#2


2  

as I've said, I strongly doesn't recommend this pratice, but its possible, bellow the code that allows to do it, let's go:

正如我说过的,我强烈不推荐这种做法,但它有可能,让允许这样做的代码咆哮,让我们开始:

To become it's possible I abused the use from TaskCompletionSource, this class allow us to set manually result in a task.

为了使我滥用TaskCompletionSource的用法成为可能,这个类允许我们手工设置任务的结果。

  • First we need to create a structure to encapsulate the process:

    首先,我们需要创建一个结构来封装流程:

    public class Process
    {
         // this dictionary store the current process running status, you will use it to define the future answer from the user interaction
         private static Dictionary<string, Answare> StatusReport = new Dictionary<string, Answare>();
         // this property is the secret to allow us wait for the ShowYesNoQuestion call, because til this happen the server doesn't send a response for the client.
         TaskCompletionSource<bool> AwaitableResult { get; } = new      TaskCompletionSource<bool>(true);
         // here we have the question to interact with the user
         IQuestionBox QuestionBox { get; set; }
    
         // this method, receive your bussiness logical the receive your question as a parameter
         public IQuestionBox Run(Action<IQuestionBox> action)
         {
             QuestionBox = new QuestionBox(this);
             // here we create a task to execute your bussiness logical processment
             Task.Factory.StartNew(() =>
             {
                action(QuestionBox);
             });
             // and as I said we wait the result from the processment
             Task.WaitAll(AwaitableResult.Task);
             // and return the question box to show the messages for the users
             return QuestionBox;
         }
    
         // this method is responsable to register a question to receive future answers, as you can see, we are using our static dictionary to register them
         public void RegisterForAnsware(string id)
         {
            if (StatusReport.ContainsKey(id))
               return;
            StatusReport.Add(id, new Answare()
            {
            });
         }
    
         // this method will deliver an answer for this correct context based on the id
         public Answare GetAnsware(string id)
         {
             if (!StatusReport.ContainsKey(id))
               return Answare.Empty;
             return StatusReport[id];
         }
    
         // this method Releases the processment
         public void Release()
         {
             AwaitableResult.SetResult(true);
         }
    
         // this method end the process delivering the response for the user
         public void End(object userResponse)
         {
            if (!StatusReport.ContainsKey(QuestionBox.Id))
               return;
            StatusReport[QuestionBox.Id].UserResponse(userResponse);
         }
    
         // this method define the answer based on the user interaction, that allows the process continuing from where it left off
         public static Task<object> DefineAnsware(string id, bool result)
         {
             if (!StatusReport.ContainsKey(id))
               return Task.FromResult((object)"Success on the operation");
             // here I create a taskcompletaionsource to allow get the result of the process, and send for the user, without it would be impossible to do it
             TaskCompletionSource<object> completedTask = new           TaskCompletionSource<object>();
             StatusReport[id] = new Answare(completedTask)
             {
                HasAnswared = true,
                Value = result
             };
             return completedTask.Task;
         }
    }
    
  • After that the question implementation

    然后是问题的实现

    public interface IQuestionBox
    {
        string Id { get; }
        Task<bool> ShowYesNoQuestionBox(string question);
        HtmlString ShowMessage();
    }
    
    class QuestionBox : IQuestionBox
    {
        Process CurrentProcess { get; set; }
    
        public string Id { get; } = Guid.NewGuid().ToString();
        private string Question { get; set; }
    
        public QuestionBox(Process currentProcess)
        {
            CurrentProcess = currentProcess;
            CurrentProcess.RegisterForAnswer(this.Id);
        }
    
        public Task<bool> ShowYesNoQuestionBox(string question)
        {
            Question = question;
            CurrentProcess.Release();
            return AwaitForAnswer();
        }
    
        public HtmlString ShowMessage()
        {
            HtmlString htm = new HtmlString(
                $"<script>showMessage('{Question}', '{Id}');</script>"
            );
    
            return htm;
        }
    
        private Task<bool> AwaitForAnswer()
        {
            TaskCompletionSource<bool> awaitableResult = new TaskCompletionSource<bool>(true);
    
            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    Thread.Sleep(2000);
                    var answare = CurrentProcess.GetAnswer(this.Id);
                    if (!answare.HasAnswered)
                        continue;
                    awaitableResult.SetResult(answare.Value);
                    break;
                }
            });
    
            return awaitableResult.Task;
        }
    }
    

The differences for yours implementaion are:

你的实施者的不同之处是:

1 - I create an Identifier to know for who I have to send the aswer, or just to stop the process.

1 -我创建一个标识符,以知道我必须向谁发送aswer,或者只是为了停止这个过程。

2 - I receive a Process as parameter, because this allows us to call the method CurrentProcess.Release(); in ShowYesNoQuestion, here in specific, releases the process to send the response responsable to interact with the user.

2 -我收到一个进程作为参数,因为这允许我们调用方法CurrentProcess.Release();在ShowYesNoQuestion中,具体地说,释放发送响应响应以与用户交互的过程。

3 - I create the method AwaitForAnswer, here one more time we use from the TaskCompletionSource class. As you can see in this method we have a loop, this loop is responsable to wait for the user interaction, and til receive a response it doesn't release the process.

3 -我创建了AwaitForAnswer的方法,这里是TaskCompletionSource类。正如您在这个方法中看到的,我们有一个循环,这个循环响应等待用户交互,直到接收到一个响应它才释放进程。

4 - I create the method ShowMessage that create a simple html script alert to simulate the user interaction.

4 -我创建了方法ShowMessage,该方法创建了一个简单的html脚本警报,以模拟用户交互。

  • Then a simple process class as you should be in your bussiness logical:

    然后是一个简单的过程类,你应该在你的商业逻辑中:

    public class SaleService
    {
       public async Task<string> GetValue(IQuestionBox qbox)
       {
          if (await qbox.ShowYesNoQuestionBox("Do you think Edney is the big guy ?"))
          {
               return "I knew, Edney is the big guy";
          }
          return "No I disagree";
       }
    }
    
  • And then the class to represent the user answer

    然后表示用户答案的类

    public class Answer
    {
        // just a sugar to represent empty responses
        public static Answer Empty { get; } = new Answer { Value = true, HasAnswered = true };
    
        public Answer()
        {
    
        }
    
        // one more time abusing from TaskCompletionSource<object>, because with this guy we are abble to send the result from the process to the user
        public Answer(TaskCompletionSource<object> completedTask)
        {
            CompletedTask = completedTask;
        }
    
        private TaskCompletionSource<object> CompletedTask { get; set; }
    
        public bool Value { get; set; }
        public bool HasAnswered { get; set; }
    
        // this method as you can see, will set the result and release the task for the user
        public void UserResponse(object response)
        {
            CompletedTask.SetResult(response);
        }
    }
    
  • Now we use all the entire structure create for this:

    现在我们用整个结构来做这个:

    [HttpPost]
    public IActionResult Index(string parametro)
    {
        // create your process an run it, passing what you want to do
        Process process = new Process();
        var question = process.Run(async (questionBox) =>
        {
            // we start the service
            SaleService service = new SaleService();
            // wait for the result
            var result = await service.GetValue(questionBox);
            // and close the process with the result from the process
            process.End(result);
        });
    
        return View(question);
    }
    
    // here we have the method that deliver us the user response interaction
    [HttpPost]
    public async Task<JsonResult> Answer(bool result, string id)
    {
        // we define the result for an Id on the process
        var response = await Process.DefineAnswer(id, result);
        // get the response from process.End used bellow
        // and return to the user
        return Json(response);
    }
    
  • and in your view

    在你看来

     <!-- Use the question as the model  page -->
     @model InjetandoInteracaoComUsuario.Controllers.IQuestionBox
     <form asp-controller="Home" asp-action="Index">
         <!-- create a simple form with a simple button to submit the home -->
         <input type="submit" name="btnDoSomething" value="All about Edney" />
     </form>
     <!-- in the scripts section we create the function that we call on the method ShowMessage, remember?-->
     <!-- this method request the action answer passing the questionbox id, and the result from a simple confirm -->
     <!-- And to finalize, it just show an  alert with the process result -->
     @section scripts{
     <script>
          function showMessage(message, id) {
             var confirm = window.confirm(message);
             $.post("/Home/Answer", { result: confirm, id: id }, function (e) {
                alert(e);
             })
          }
    
     </script>
     @Model?.ShowMessage()
    }
    

As I've said, I realy disagree with this pratices, the correct should to write a new dll, to support the web enviroment, but I hope it help you.

正如我所说的,我真的不同意这种做法,正确的做法应该写一个新的dll来支持web环境,但是我希望它能对您有所帮助。

I put the project on github to you can download an understand all the solution

我把项目放在github上,你可以下载理解所有的解决方案

I realy hope it can help you

我真的希望它能帮助你

#3


0  

It's actually much the same, except your UI is sort of disconnected and proxied with the HTTP protocol for the most part.

它实际上几乎是一样的,除了你的UI是断开连接的,并且大部分都是通过HTTP协议进行代理。

you essentially need to build the same code as your WPF code but then in the browser construct ajax calls in to the controller actions to apply your logic.

本质上,您需要构建与WPF代码相同的代码,但是在浏览器构造ajax调用时,会调用控制器操作来应用您的逻辑。

To clarify ...

阐明……

so lets say you are building up a process over a series of questions that based on the users answer you put different steps in to the process.

假设你正在构建一个过程,基于用户回答的一系列问题你在这个过程中加入了不同的步骤。

You can either ...

你可以…

  1. build the process in the database
  2. 在数据库中构建流程
  3. build it in session on the server
  4. 在服务器上的会话中构建它
  5. build it on the client as a js object
  6. 将其构建为一个js对象

then do a post for execution ofthe constructed process.

然后做一个post来执行构建的进程。

think of the "statelessness" as a series of short interactions, but the state you keep between them can be done either on the client, in a db or in the users logged in session on the web server.

将“无状态”看作是一系列的短交互,但是您在它们之间保持的状态可以在客户机上、在db中,或者在web服务器上的用户会话中进行。

#4


0  

In your controller you can add an ActionResult that will give you the html response to your jquery modal popup request. Here is an example

在您的控制器中,您可以添加一个ActionResult,它将为您的jquery模态弹出请求提供html响应。这是一个例子

public class MController : Controller {
            public ActionResult doWork(requirement IQuestionBox)
            { 
                // model is already modelBound/IOC resolved
                return PartialView("_doWork", requirement );
            }    
        }

//scripts

/ /脚本

$(function(){    
        $.ajax({
           url:"/m/doWork",
           type:"get",
           success:function(data){
               $modal.html(data); // bind to modal
           }
        });
});

Apologies for not fully understanding the question.

很抱歉没有完全理解这个问题。

hope this helps!

希望这可以帮助!

#1


1  

You can create a web socket connection from client side to server side. And work with front-end content with web socket request. It could be implemented as following:

您可以创建从客户端到服务器端的web套接字连接。使用web socket请求处理前端内容。它可以实现如下:

Client side:

客户端:

$app = {
    uiEventsSocket : null,
    initUIEventsConnection : function(url) {
        //create a web socket connection
        if (typeof (WebSocket) !== 'undefined') {
                this.uiEventsSocket = new WebSocket(url);
            } else if (typeof (MozWebSocket) !== 'undefined') {
                this.uiEventsSocket = new MozWebSocket(url);
            } else {
                console.error('WebSockets unavailable.');
            }

            //notify if there is an web socket error
            this.uiEventsSocket.onerror = function () {
                console.error('WebSocket raised error.');
            }

            this.uiEventsSocket.onopen = function () {
                console.log("Connection to " + url + " established");
            }

            //handling message from server side
            this.uiEventsSocket.onmessage = function (msg) {
                this._handleMessage(msg.data);
            };
    },

    _handleMessage : function(data){
        //the message should be in json format
        //the next line fails if it is not
        var command = JSON.parse(data);

        //here is handling the request to show prompt
        if (command.CommandType == 'yesNo') {
            var message = command.Message;

            var result = confirm(message);
            //not sure that bool value will be successfully converted
            this.uiEventsSocket.send(result ? "true" : "false");
        }
    }
}

And init it from ready or load event:

从就绪或加载事件初始化:

window.onload = function() { $app.initUIEventsConnection(yourUrl); }

Note that you url should begin with ws:// instead of http:// and wss:// instead of https:// (Web Sockets and Web Sockets Secure).

注意,url应该从ws开始://而不是http://和wss://而不是https:// (Web Sockets和Web Sockets Secure)。

Server side.

服务器端。

Here is a good article for how to setup web sockets at asp.net core application or you could find another one. Note that you should group web socket connections from single user and if you want to send a message to the concrete user, you should send message for every connection from this user.

这里有一篇很好的文章,介绍如何在asp.net核心应用程序中设置web sockets,或者您可以找到另一个。注意,您应该将web socket连接从单个用户分组,如果您想向具体的用户发送消息,您应该向该用户的每个连接发送消息。

Every web socket you should accept with AcceptWebSocketAsync() method call and then add instance of this web socket to singleton, which contains a set of web sockets connection groupped by user.

您应该使用AcceptWebSocketAsync()方法调用接受的每个web套接字,然后将这个web套接字的实例添加到singleton中,singleton包含一组由用户分组的web套接字连接。

The following class will be used to operate commands:

下面的类将用于操作命令:

public class UICommand
{
    public string CommandType { get; set; }
    public string Message { get; set; }
    public Type ReturnType { get; set; }
}

And a full code of singleton for handling sockets

以及用于处理套接字的单例的完整代码

public class WebSocketsSingleton
{
    private static WebSocketsSingleton _instance = null;
    //here stored web sockets groupped by user
    //you could use user Id or another marker to exactly determine the user
    private Dictionary<string, List<WebSocket>> _connectedSockets;

    //for a thread-safety usage
    private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();

    public static WebSocketsSingleton Instance {
        get {
            if (this._instance == null)
            {
                this._instance = new WebSocketsSingleton();
            }

            return this._instance;
        }
    }

    private WebSocketsSingleton()
    {
        this._connectedSockets = new Dictionary<string, List<WebSocket>>();
    }

    /// <summary>
    /// Adds a socket into the required collection
    /// </summary>
    public void AddSocket(string userName, WebSocket ws)
    {
        if (!this._connectedSockets.ContainsKey(userName))
        {
            Locker.EnterWriteLock();
            try
            {
                this._connectedSockets.Add(userName, new List<WebSocket>());
            }
            finally
            {
                Locker.ExitWriteLock();
            }
        }

        Locker.EnterWriteLock();
        try
        {
            this._connectedSockets[userName].Add(ws);
        }
        finally
        {
            Locker.ExitWriteLock();
        }
    }

    /// <summary>
    /// Sends a UI command to required user
    /// </summary>  
    public async Task<string> SendAsync(string userName, UICommand command)
    {
        if (this._connectedSockets.ContainsKey(userName))
        {
            var sendData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(command));

        foreach(var item in this._connectedSockets[userName])
        {
            try
            {
                await item.SendAsync(new ArraySegment<byte>(sendData), WebSocketMessageType.Text, true, CancellationToken.None);
            }
            catch (ObjectDisposedException)
            {
                //socket removed from front end side
            }
        }

            var buffer = new ArraySegment<byte>(new byte[1024]);
            var token = CancellationToken.None;         
            foreach(var item in this._connectedSockets[userName])
            {
                await Task.Run(async () => {
                    var tempResult = await item.ReceiveAsync(buffer, token);
                    //result received
                    token = new CancellationToken(true);
                });
            }

            var resultStr = Encoding.Utf8.GetString(buffer.Array);

            if (command.ReturnType == typeof(bool))
            {
                return resultStr.ToLower() == "true";
            }

            //other methods to convert result into required type

            return resultStr;
        }

        return null;
    }
}

Explanation:

解释:

  • on establishing connection from web socket it will be added with AddSocket method
  • 在从web套接字建立连接时,将使用AddSocket方法进行添加
  • on sending request to show a message, the required command will be passed into SendAsync method
  • 在发送显示消息的请求时,所需的命令将被传递到SendAsync方法中
  • the command will be serialized to JSON (using Json.Net, however you could serialize in your way) and send to all sockets, related to the required user
  • 该命令将被序列化为JSON(使用JSON)。Net,但是您可以以自己的方式序列化)并发送到与所需用户相关的所有套接字
  • after the command sent, application will wait for respond from front end side
  • 发送命令后,应用程序将等待前端的响应
  • the result will be converted to required type and sent back to your IQuestionBox
  • 结果将被转换为所需的类型并发送回您的IQuestionBox

In the web socket handling your should add some kind of the following code:

在处理web套接字时,您应该添加以下代码:

app.Use(async (http, next) =>
{
    if (http.WebSockets.IsWebSocketRequest)
    {
        var webSocket = await http.WebSockets.AcceptWebSocketAsync();
        var userName = HttpContext.Current.User.Identity.Name;
        WebSocketsSingleton.Instance.AddSocket(userName, webSocket);

        while(webSocket.State == WebSocketState.Open)
        {
            //waiting till it is not closed         
        }

        //removing this web socket from the collection
    }
});

And your method implementation of ShowYesNoQuestionBox should be kind of following:

你的ShowYesNoQuestionBox的方法实现应该是这样的:

public async Task<bool> ShowYesNoQuestionBox(string userName, string text)
{
    var command = new UICommand
    {
        CommandType = "yesNo",
        Message = text,
        ReturnType = typeof(bool)
    };

    return await WebSocketsSingleton.Instance.SendAsync(string userName, command);
}

Note that there should be added userName to prevent sending the same message to all of the connected users.

注意,应该添加用户名,以防止向所有连接的用户发送相同的消息。

WebSocket should create the persistent connection between server and client sides, so you could simply send commands in two ways.

WebSocket应该在服务器端和客户端之间创建持久连接,因此您可以用两种方式发送命令。

I am kindly new to Asp.Net Core, so the final implementation could be a bit different from this.

我对Asp很有兴趣。Net Core,所以最终实现可能与这个有所不同。

#2


2  

as I've said, I strongly doesn't recommend this pratice, but its possible, bellow the code that allows to do it, let's go:

正如我说过的,我强烈不推荐这种做法,但它有可能,让允许这样做的代码咆哮,让我们开始:

To become it's possible I abused the use from TaskCompletionSource, this class allow us to set manually result in a task.

为了使我滥用TaskCompletionSource的用法成为可能,这个类允许我们手工设置任务的结果。

  • First we need to create a structure to encapsulate the process:

    首先,我们需要创建一个结构来封装流程:

    public class Process
    {
         // this dictionary store the current process running status, you will use it to define the future answer from the user interaction
         private static Dictionary<string, Answare> StatusReport = new Dictionary<string, Answare>();
         // this property is the secret to allow us wait for the ShowYesNoQuestion call, because til this happen the server doesn't send a response for the client.
         TaskCompletionSource<bool> AwaitableResult { get; } = new      TaskCompletionSource<bool>(true);
         // here we have the question to interact with the user
         IQuestionBox QuestionBox { get; set; }
    
         // this method, receive your bussiness logical the receive your question as a parameter
         public IQuestionBox Run(Action<IQuestionBox> action)
         {
             QuestionBox = new QuestionBox(this);
             // here we create a task to execute your bussiness logical processment
             Task.Factory.StartNew(() =>
             {
                action(QuestionBox);
             });
             // and as I said we wait the result from the processment
             Task.WaitAll(AwaitableResult.Task);
             // and return the question box to show the messages for the users
             return QuestionBox;
         }
    
         // this method is responsable to register a question to receive future answers, as you can see, we are using our static dictionary to register them
         public void RegisterForAnsware(string id)
         {
            if (StatusReport.ContainsKey(id))
               return;
            StatusReport.Add(id, new Answare()
            {
            });
         }
    
         // this method will deliver an answer for this correct context based on the id
         public Answare GetAnsware(string id)
         {
             if (!StatusReport.ContainsKey(id))
               return Answare.Empty;
             return StatusReport[id];
         }
    
         // this method Releases the processment
         public void Release()
         {
             AwaitableResult.SetResult(true);
         }
    
         // this method end the process delivering the response for the user
         public void End(object userResponse)
         {
            if (!StatusReport.ContainsKey(QuestionBox.Id))
               return;
            StatusReport[QuestionBox.Id].UserResponse(userResponse);
         }
    
         // this method define the answer based on the user interaction, that allows the process continuing from where it left off
         public static Task<object> DefineAnsware(string id, bool result)
         {
             if (!StatusReport.ContainsKey(id))
               return Task.FromResult((object)"Success on the operation");
             // here I create a taskcompletaionsource to allow get the result of the process, and send for the user, without it would be impossible to do it
             TaskCompletionSource<object> completedTask = new           TaskCompletionSource<object>();
             StatusReport[id] = new Answare(completedTask)
             {
                HasAnswared = true,
                Value = result
             };
             return completedTask.Task;
         }
    }
    
  • After that the question implementation

    然后是问题的实现

    public interface IQuestionBox
    {
        string Id { get; }
        Task<bool> ShowYesNoQuestionBox(string question);
        HtmlString ShowMessage();
    }
    
    class QuestionBox : IQuestionBox
    {
        Process CurrentProcess { get; set; }
    
        public string Id { get; } = Guid.NewGuid().ToString();
        private string Question { get; set; }
    
        public QuestionBox(Process currentProcess)
        {
            CurrentProcess = currentProcess;
            CurrentProcess.RegisterForAnswer(this.Id);
        }
    
        public Task<bool> ShowYesNoQuestionBox(string question)
        {
            Question = question;
            CurrentProcess.Release();
            return AwaitForAnswer();
        }
    
        public HtmlString ShowMessage()
        {
            HtmlString htm = new HtmlString(
                $"<script>showMessage('{Question}', '{Id}');</script>"
            );
    
            return htm;
        }
    
        private Task<bool> AwaitForAnswer()
        {
            TaskCompletionSource<bool> awaitableResult = new TaskCompletionSource<bool>(true);
    
            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    Thread.Sleep(2000);
                    var answare = CurrentProcess.GetAnswer(this.Id);
                    if (!answare.HasAnswered)
                        continue;
                    awaitableResult.SetResult(answare.Value);
                    break;
                }
            });
    
            return awaitableResult.Task;
        }
    }
    

The differences for yours implementaion are:

你的实施者的不同之处是:

1 - I create an Identifier to know for who I have to send the aswer, or just to stop the process.

1 -我创建一个标识符,以知道我必须向谁发送aswer,或者只是为了停止这个过程。

2 - I receive a Process as parameter, because this allows us to call the method CurrentProcess.Release(); in ShowYesNoQuestion, here in specific, releases the process to send the response responsable to interact with the user.

2 -我收到一个进程作为参数,因为这允许我们调用方法CurrentProcess.Release();在ShowYesNoQuestion中,具体地说,释放发送响应响应以与用户交互的过程。

3 - I create the method AwaitForAnswer, here one more time we use from the TaskCompletionSource class. As you can see in this method we have a loop, this loop is responsable to wait for the user interaction, and til receive a response it doesn't release the process.

3 -我创建了AwaitForAnswer的方法,这里是TaskCompletionSource类。正如您在这个方法中看到的,我们有一个循环,这个循环响应等待用户交互,直到接收到一个响应它才释放进程。

4 - I create the method ShowMessage that create a simple html script alert to simulate the user interaction.

4 -我创建了方法ShowMessage,该方法创建了一个简单的html脚本警报,以模拟用户交互。

  • Then a simple process class as you should be in your bussiness logical:

    然后是一个简单的过程类,你应该在你的商业逻辑中:

    public class SaleService
    {
       public async Task<string> GetValue(IQuestionBox qbox)
       {
          if (await qbox.ShowYesNoQuestionBox("Do you think Edney is the big guy ?"))
          {
               return "I knew, Edney is the big guy";
          }
          return "No I disagree";
       }
    }
    
  • And then the class to represent the user answer

    然后表示用户答案的类

    public class Answer
    {
        // just a sugar to represent empty responses
        public static Answer Empty { get; } = new Answer { Value = true, HasAnswered = true };
    
        public Answer()
        {
    
        }
    
        // one more time abusing from TaskCompletionSource<object>, because with this guy we are abble to send the result from the process to the user
        public Answer(TaskCompletionSource<object> completedTask)
        {
            CompletedTask = completedTask;
        }
    
        private TaskCompletionSource<object> CompletedTask { get; set; }
    
        public bool Value { get; set; }
        public bool HasAnswered { get; set; }
    
        // this method as you can see, will set the result and release the task for the user
        public void UserResponse(object response)
        {
            CompletedTask.SetResult(response);
        }
    }
    
  • Now we use all the entire structure create for this:

    现在我们用整个结构来做这个:

    [HttpPost]
    public IActionResult Index(string parametro)
    {
        // create your process an run it, passing what you want to do
        Process process = new Process();
        var question = process.Run(async (questionBox) =>
        {
            // we start the service
            SaleService service = new SaleService();
            // wait for the result
            var result = await service.GetValue(questionBox);
            // and close the process with the result from the process
            process.End(result);
        });
    
        return View(question);
    }
    
    // here we have the method that deliver us the user response interaction
    [HttpPost]
    public async Task<JsonResult> Answer(bool result, string id)
    {
        // we define the result for an Id on the process
        var response = await Process.DefineAnswer(id, result);
        // get the response from process.End used bellow
        // and return to the user
        return Json(response);
    }
    
  • and in your view

    在你看来

     <!-- Use the question as the model  page -->
     @model InjetandoInteracaoComUsuario.Controllers.IQuestionBox
     <form asp-controller="Home" asp-action="Index">
         <!-- create a simple form with a simple button to submit the home -->
         <input type="submit" name="btnDoSomething" value="All about Edney" />
     </form>
     <!-- in the scripts section we create the function that we call on the method ShowMessage, remember?-->
     <!-- this method request the action answer passing the questionbox id, and the result from a simple confirm -->
     <!-- And to finalize, it just show an  alert with the process result -->
     @section scripts{
     <script>
          function showMessage(message, id) {
             var confirm = window.confirm(message);
             $.post("/Home/Answer", { result: confirm, id: id }, function (e) {
                alert(e);
             })
          }
    
     </script>
     @Model?.ShowMessage()
    }
    

As I've said, I realy disagree with this pratices, the correct should to write a new dll, to support the web enviroment, but I hope it help you.

正如我所说的,我真的不同意这种做法,正确的做法应该写一个新的dll来支持web环境,但是我希望它能对您有所帮助。

I put the project on github to you can download an understand all the solution

我把项目放在github上,你可以下载理解所有的解决方案

I realy hope it can help you

我真的希望它能帮助你

#3


0  

It's actually much the same, except your UI is sort of disconnected and proxied with the HTTP protocol for the most part.

它实际上几乎是一样的,除了你的UI是断开连接的,并且大部分都是通过HTTP协议进行代理。

you essentially need to build the same code as your WPF code but then in the browser construct ajax calls in to the controller actions to apply your logic.

本质上,您需要构建与WPF代码相同的代码,但是在浏览器构造ajax调用时,会调用控制器操作来应用您的逻辑。

To clarify ...

阐明……

so lets say you are building up a process over a series of questions that based on the users answer you put different steps in to the process.

假设你正在构建一个过程,基于用户回答的一系列问题你在这个过程中加入了不同的步骤。

You can either ...

你可以…

  1. build the process in the database
  2. 在数据库中构建流程
  3. build it in session on the server
  4. 在服务器上的会话中构建它
  5. build it on the client as a js object
  6. 将其构建为一个js对象

then do a post for execution ofthe constructed process.

然后做一个post来执行构建的进程。

think of the "statelessness" as a series of short interactions, but the state you keep between them can be done either on the client, in a db or in the users logged in session on the web server.

将“无状态”看作是一系列的短交互,但是您在它们之间保持的状态可以在客户机上、在db中,或者在web服务器上的用户会话中进行。

#4


0  

In your controller you can add an ActionResult that will give you the html response to your jquery modal popup request. Here is an example

在您的控制器中,您可以添加一个ActionResult,它将为您的jquery模态弹出请求提供html响应。这是一个例子

public class MController : Controller {
            public ActionResult doWork(requirement IQuestionBox)
            { 
                // model is already modelBound/IOC resolved
                return PartialView("_doWork", requirement );
            }    
        }

//scripts

/ /脚本

$(function(){    
        $.ajax({
           url:"/m/doWork",
           type:"get",
           success:function(data){
               $modal.html(data); // bind to modal
           }
        });
});

Apologies for not fully understanding the question.

很抱歉没有完全理解这个问题。

hope this helps!

希望这可以帮助!