Vue + SignalR + .NET Core 2.2 通讯

1. 引用SignalR

服务端略

Vue 添加 依赖 @microsoft/signalr

npm install @microsoft/signalr

2. 服务端注册使用

ConfigureServices 中加入 AddHttpContextAccessor 和 AddSignalR

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });
            //获取连接ID用的
            services.AddHttpContextAccessor();

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            //添加SignalR
            services.AddSignalR();

            //MQ的注入
            services.AddSingleton<IMyMQ, MyMQ>();

        }

Configure中

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            /*这里引用的顺序如果不对会报跨域错误*/

            app.UseCors(builder => {
                //builder.WithOrigins(""http://127.0.0.1:44325");//指定IP
                //builder.AllowAnyOrigin();//这个不好使
                builder.SetIsOriginAllowed(origin => true);
                builder.AllowAnyHeader();
                builder.AllowAnyMethod();
                builder.AllowCredentials();
            });

            app.UseWebSockets();

            //取个名Home 这个随便 前端跟他一样就行
            //ViewHub 是新建的类
            app.UseSignalR(routes =>
            {
                routes.MapHub<ViewHub>("/Home");
            });

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();


            //app.UseSession();
            
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });


        }
    }

ViewHub 类继承 Hub

    public class ViewHub : Hub
    {
        /// <summary>
        /// websocket SignalR
        /// </summary>
        private readonly IHubContext<ViewHub> _messageHub;
        /// <summary>
        /// MQ
        /// </summary>
        private readonly IMyMQ _mymq;

        
        private readonly IHttpContextAccessor _accessor;

        //第一个是我的MQ注入,第二个是连接的注入
        public ViewHub(IMyMQ mymq, IHubContext<ViewHub> messageHub,IHttpContextAccessor accessor)
        {
            this._mymq = mymq;
            this._messageHub = messageHub;
            this._accessor = accessor;
            //发送消息
            messageHub.Clients.All.SendAsync("MQLOG", "XXXX");
        }

        public override Task OnConnectedAsync()
        {
            //可以拿到当前连接的ID 作为标识
            string connId = Context.ConnectionId;

            //在连接时还可以传递其他信息
            //var token = _accessor.HttpContext.Request.Query["access_token"];

            //给当前连接返回消息 .Clients可以发多个连接ID
            Clients.Client(connId).SendAsync("ConnectResponse", id+"连接成功");
            
            return base.OnConnectedAsync();
        }

        //接收前端来的消息
        public async Task SendMessage(string theme, string data,string GUID)
        {
            JObject msg = new JObject();
            msg.Add("msg","发送成功");
            msg.Add("GUID", GUID);
            //也可以这样直接发送消息
            await Clients.All.SendAsync("MQSend", msg.ToString());
        }
    }

3. Vue端连接使用

建一个signalR.js

import * as signalR from "@microsoft/signalr";

//Url 注意/Home 跟服务端一致
const url = "http://localhost:63437/Home";

const signal = new signalR.HubConnectionBuilder() //服务器地址
  .withUrl(url, { 
      //传递Token
      accessTokenFactory: () => "00001" 
    })
  .withAutomaticReconnect()//自动重连
  .build();
async function start() {
  try {
    await signal.start();
    console.log("connected");
  } catch (err) {
    console.log(err);
    //半自动重连
    //setTimeout(() => start(), 5000);
  }
}

//关闭
signal.onclose(async err => {
  //await start();
  console.log(err);
});

//将创建的signal赋值给Vue实例
export default {
  //install方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。
  install: function(Vue) {
    Vue.prototype.signalr = signal;
  }
};

在main.js中引用

import signalr from "./utils/signalR";
Vue.use(signalr);

连接,这里写在了App.vue中

  created() {
    //接收消息建议在start之前注册,至少注册一个
    //参照服务端 连接之后会有个ConnectResponse返回ID
    this.signalr.on("ConnectResponse", res => {
      //可以做相关业务逻辑
      console.log(res);
    });
    this.signalr.on("MQSend", res => {
        res = JSON.parse(res);
    });
  },
  mounted() {
    this.signalr.start().then(() => {
        console.log("连接");
    });
  },
  methods: {
    //向服务端发送消息
    async setMessage(theme, data) {
      return new Promise((resolve, reject) => {
          //对应服务端的SendMessage方法
          this.signalr
            .invoke("SendMessage", theme, JSON.stringify(data), guid)
            .catch(function(err) {
              console.error(err.toString())
              //reject(err.toString());
            });
          resolve('done')
      });
    });
  }

4. 其他-invoke的响应

到这就可以了,但是使用中 invoke 是不返回消息的,如果返回消息还要再下发,在 on 里接收,这是两个独立事件,业务页面我要先写个监听返回再写个发送方法,如果我能写成 await 形式就方便了,比如

let res = await this.App.setMessage("XXX", {XXXX});
this.$message(res.msg);

这里要将App.vue中的setMessage重写,有返回结果后再执行Promise的resolve,

async setMessage(theme, data) {
    //先取一个标识
    const guid = this.$newGUID();
    //存储当前的Promise
    let prom = {};
    //创建Promise 将GUID一并上传
    let p = new Promise((resolve, reject) => {
      this.signalr
        .invoke("SendMessage", theme, JSON.stringify(data), guid)
      //将resolve 缓存
      prom.resolve = resolve;
    });
    //将Promise缓存
    prom.p = p;
    //放到页面缓存
    this.MQsend[guid] = prom;
    //由于没有执行resolve 这个请求将一直挂起 直到执行resolve
    return p;
}

App.vue接收结果改为

this.signalr.on("MQSend", res => {
    
    res = JSON.parse(res);

    /**
    *服务返回GUID
    *先找到缓存中GUID对应的Promise 
    *再执行resolve 执行后删除缓存 
    *这时Promise的状态改变 await结束
    */
    if (res.GUID && this.MQsend[res.GUID]) {
      this.MQsend[res.GUID].resolve(res);
      delete this.MQsend[res.GUID];
    }
});