简体   繁体   English

使用SignalR推送事件“DB Data changes”

[英]PUSH on event “DB Data Changes” using SignalR

All right I followed the chat application demo just fine, a couple of other tuts as well, but all of them combined doesn't answer or due to my lack of intelligence to derive the answer I want. 好的,我按照聊天应用程序演示很好地进行了,还有其他一些练习,但是所有这些组合都无法回答,或者由于我缺乏智能来得出想要的答案而已。 The scenario is as follows 方案如下

I am building a website with 我正在建立一个网站

  1. MVC 4 MVC 4
  2. .NET Framework 4.0 .NET Framework 4.0
  3. IIS 7.5 IIS 7.5
  4. ASP.NET 4 ASP.NET 4
  5. SQL Server 2008 R2 SQL Server 2008 R2

that is to accompany a desktop app, ie my web application and the desktop app will be interacting with the same DB. 这是伴随桌面应用程序,即我的Web应用程序和桌面应用程序将与同一个DB交互。 I want to add an AMS (Acess Management System) to the website and the desktop application, so that Users access rights to any function inside the apps can be managed finely grained. 我想向网站和桌面应用程序添加AMS(访问管理系统),以便可以细粒度管理用户对应用程序内任何功能的访问权限。 I want the changes to be PUSHED to the wesite in realtime. 我希望实时将更改推送给wesite。

For Example: A manager from the desktop app revokes the rights of the clerks to view sales reports, and a couple of clerks were online on the website, so I want those changes to be PUSHED to the website as they are made. 例如:来自桌面应用程序的经理撤销了职员查看销售报告的权利,并且有几个职员在网站上在线,因此我希望这些更改在网站制作时被推送到网站。

Now currently I am storing the rights of the users at login, so that with the increasing number of users wont cause the performance of the website to degrade quickly, now I know that when checking for every action either allowed or not, I can make a round trip to the DB and check for the condition, but as I said earlier, that would degrade the performance. 现在我正在登录时存储用户的权限,因此随着越来越多的用户不会导致网站的性能迅速降低,现在我知道在检查允许或不允许的每个操作时,我可以制作一个往返数据库并检查条件,但是正如我之前所说,这会降低性能。

Now I want to PUSH the rights of the users to the website if there are some changes, either from the website itself or the desktop app, I looked into SignalR, and I found a solution here on SO ( This and This ), but I don't want a clock ticker running continuously and then broadcasting the changes to all connected clients. 现在我想推送用户对网站的权利,如果有一些变化,无论是从网站本身还是桌面应用程序,我查看了SignalR,我在SO( This and This )上找到了解决方案,但是我不想让时钟行情记录器连续运行,然后将更改广播到所有连接的客户端。 And the user that is being changed, maybe connected to the website or maybe not. 并且正在更改的用户可能连接到网站或者可能没有。 Please can someone point me in the right direction 请有人指出我正确的方向

I have spent much time on trying to find a solution for this, and the simplest I've found, using SignalR, is to use Hubs as a gateway to your Repository/API: 我花了很多时间试图为此找到解决方案,而我发现使用SignalR最简单的方法是使用Hub作为Repository / API的网关:

So, here's how the project would be set up: 那么,这是项目的设置方式:

  1. ASP.NET MVC Controllers' Actions dish out entire pages. ASP.NET MVC控制器的动作完成了整个页面。

     public class HomeController : Controller { // // GET: /Home/ public ActionResult Index() { return View(); } } 
  2. The View should be wrapped in a Layout that loads a Knockout MVVM. View应该包含在一个加载Knockout MVVM的Layout中。 The View then initializes the part of the MVVMs that need to be used (as in, lump all your MVVM script in one file, and the View initializes the SignalR connections, to avoid needless connections (the code below has the Script initializing itself)). View然后初始化需要使用的MVVM部分(例如,将所有MVVM脚本集中在一个文件中,并且View初始化SignalR连接,以避免不必要的连接(下面的代码让脚本初始化)) 。 The View also has KnockOut bindings attached to it. 视图还具有附加的KnockOut绑定。

MVVM: MVVM:

function AddressViewModel(rid, nick, line1, line2, city, state, zip)
{
    <!-- Modifiable Properties should be observable. This will allow Hub updates to flow to the View -->
    var self = this;
    self.rid = rid;
    self.nick = ko.observable(nick);
    self.line1 = ko.observable(line1);
    self.line2 = ko.observable(line2);
    self.city = ko.observable(city);
self.state = ko.observable(new StateViewModel(state.RID, state.Title, state.Abbreviation));
    self.zip = ko.observable(zip);
}
function StateViewModel(rid, title, abbreviation)
{
    <!-- States are a stagnant list. These will not be updated -->
    var self = this;
    self.rid = rid;
    self.title = title;
    self.abbreviation = abbreviation;
}
var Page = new function()
{

    //Page holds all Page's View Models. The init function can be modified to start only certain hubs.
    var page = this;
    page.init = function()
    {
        page.Account.init();
    }
    page.Account = new function ()
    {
            //Account holds account-specific information. Should only be retrieved on an encrypted, secure, and authorized connection. 
        account.init = function()
        {
            account.Addresses.init();
        }
        //Addresses manages the calls to Hubs and their callbacks to modify local content.
        account.Addresses = new function ()
        {
                    //Connect to the hub, and create an observable list.
            var addresses = this;
            addresses.hub = $.connection.accountAddressHub;
            addresses.list = ko.observableArray([]);

                    //Called on initial load. This calls the Index() function on the Hub.
            addresses.init = function ()
            {
                addresses.hub.server.index();
            }
                //displayMode allows for dynamic changing of the template.
            addresses.displayMode = ko.computed(function ()
            {
                return 'Address';
            });
                    //Empty allows to prompt user instead of just showing a blank screen.
            addresses.empty = ko.computed(function ()
            {
                if (addresses.list().length == 0)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            });
                    //During initial load, unless if MVC provides the information with the View, the list will be empty until the first SignalR callback. This allows us to prompt the user we're still loading.
            addresses.loading = ko.observable(true);
                //The Hub's Index function ought to reach indexBack with a list of addresses. The addresses are then mapped to the list, using the local AddressViewModel. Sets initial load to false, as we now have addresses.
            addresses.hub.client.indexBack = function (addressList)
            {
                $.map(addressList, function (address)
                {
                    addresses.list.push(new AddressViewModel(address.RID, address.Nick, address.Line1, address.Line2, address.City, address.State, address.ZIP));
                });
                addresses.loading(false);
            }
         }
      }
}

On Run Script (Place in Layout, Script File, or View, depending on needs or confirgurations per page) 在运行脚本(放置在布局,脚本文件或视图中,取决于每页的需求或确认)

$(function ()
{
    //Configures what SignalR will do when starting, on receive, reconnected, reconnected, or disconnected. 
    $.connection.hub.starting(function ()
    {
        $('.updated').hide();
        $('.updating').show();
    });
    $.connection.hub.received(function ()
    {
        $('.updating').hide();
        $('.updated').show();
    });
    $.connection.hub.reconnecting(function ()
    {
        $('.updated').hide();
        $('.updating').show();
    });
    $.connection.hub.reconnected(function ()
    {
        $('.updating').hide();
        $('.updated').show();
    });
    //This will keep attempt to reconnect - the beauty of this, if the user unplugs the internet with page loaded, and then plugs in, the client reconnects automatically. However, the client would probably not receive any backlog - I haven't test that.
    $.connection.hub.disconnected(function ()
    {
        setTimeout(function ()
        {
            $.connection.hub.start();
        }, 5000); // Restart connection after 5 seconds.
    });
    //Apply knockout bindings, using the Page function from above.
    ko.applyBindings(Page);
    //Start the connection. 
    $.connection.hub.start(function ()
    {
    }).done(function ()
    {
            //If successfully connected, call the init functions, which propagate through the script to connect to all the necessary hubs.
        console.log('Connected to Server!');
        Page.init();
    })
    .fail(function ()
    {
        console.log('Could not Connect!');
    });;
});

LayOut: 布局:

<!DOCTYPE html>
<html>
    <head>
        . . .
        @Styles.Render( "~/Content/css" )
        <!-- Load jQuery, KnockOut, and your MVVM scripts. -->
        @Scripts.Render( "~/bundles/jquery" )
        <script src="~/signalr/hubs"></script>
        . . .
    </head>
    <body id="body" data-spy="scroll" data-target="#sidenav">
        . . .
        <div id="wrap">
            <div class="container">
                @RenderBody()
            </div>
        </div>
        @{ Html.RenderPartial( "_Foot" ); }
    </body>
</html>

View (Index): 查看(索引):

@{
    ViewBag.Title = "My Account";
}
<div>
    @{
        Html.RenderPartial( "_AddressesWrapper" );
    }   
</div>

_AddressesWrapper: _AddressesWrapper:

<div data-bind="with: Page.Account.Addresses">
    @{
        Html.RenderPartial( "_Address" );
    }
     <div id="Addresses" class="subcontainer">
         <div class="subheader">
            <div class="subtitle">
                <h2>
                    <span class="glyphicon glyphicon-home">
                    </span>
                        Addresses
                </h2>
            </div>
        </div>
        <div id="AddressesContent" class="subcontent">
            <div class="row panel panel-primary">
                <!-- Check to see if content is empty. If empty, content may still be loading.-->
                <div data-bind="if: Page.Account.Addresses.empty">
                    <!-- Content is empty. Check if content is still initially loading -->
                    <div data-bind="if:Page.Account.Addresses.loading">
                        <!-- Content is still in the initial load. Tell Client. -->
                        <div class="well well-lg">
                            <p class="text-center">
                                <img src="@Url.Content("~/Content/Images/ajax-loader.gif")" width="50px" height="50px" />
                                <strong>We are updating your Addresses.</strong> This should only take a moment.
                            </p>
                        </div>
                    </div>
                    <div data-bind="ifnot:Page.Account.Addresses.loading">
                        <!-- Else, if not loading, the Client has no addresses. Tell Client. -->
                        <div class="well well-lg">
                            <p class="text-center">
                                <strong>You have no Addresses.</strong> If you add an Addresses, you can view, edit, and delete it here.
                            </p>
                        </div>
                    </div>
                </div>
                <!-- Addresses is not empty -->
                <div data-bind="ifnot: Page.Account.Addresses.empty">
                    <!-- We have content to display. Bind the list with a template in the Partial View we loaded earlier -->
                    <div data-bind="template: { name: Page.Account.Addresses.displayMode, foreach: Page.Account.Addresses.list }">
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

_Address: _地址:

<script type="text/html" id="Address">
    <div class="col-lg-3 col-xs-6 col-sm-4 well well-sm">
        <address>
            <strong data-bind="text: nick"></strong><br>
            <span data-bind="text: line1"></span><br>
            <span data-bind="if: line2 == null">
                <span data-bind="text: line2"></span><br>
            </span>
            <span data-bind="text: city"></span>, <span data-bind=" text: state().abbreviation"></span> <span data-bind="text: zip"></span>
        </address>
    </div>
</script>
  1. The KnockOut script interchanges with a SignalR Hub. KnockOut脚本与SignalR Hub交换。 The Hub receives the call, checks the authorization, if necessary, and passes the call to the proper repository or straight to WebAPI 2 (this example). Hub接收呼叫,在必要时检查授权,并将呼叫传递到适当的存储库或直接传递给WebAPI 2(此示例)。 The SignalR Hub action then takes the results of the API exchange and determines which function to call, and what data to pass. 然后,SignalR Hub操作获取API交换的结果,并确定要调用的函数以及要传递的数据。

     public class AccountAddressHub : AccountObjectHub { public override async Task Index() { //Connect to Internal API - Must be done within action. using( AddressController api = new AddressController(await this.Account()) ) { //Make Call to API to Get Addresses: var addresses = api.Get(); //Return the list only to Connecting ID. Clients.Client( Context.ConnectionId ).indexBack( addresses ); //Or, return to a list of specific Connection Ids - can also return to all Clients, instead of adding a parameter. Clients.Clients( ( await this.ConnectionIds() ).ToList() ).postBack( Address ); } } } 
  2. The API Controller checks data integrity and sends a callback to the same SignalR Hub action. API控制器检查数据完整性,并将回调发送到同一SignalR Hub动作。

     public class AddressController : AccountObjectController { ... // GET api/Address public ICollection<Address> Get() { //This returns back the calling Hub action. return Account.Addresses; } ... } 
  3. Your .NET application will need to use the same functions as your javascript-ran site. 您的.NET应用程序将需要使用与javascript运行站点相同的功能。 This will allow a modification from any client to then propagate to however many clients are needed (that single client who just loaded, as in this example, or to broadcast to everyone, or anywhere in between) 这将允许从任何客户端进行的修改随后传播到需要的任何客户端(如本例中那样,刚刚加载的单个客户端,或向所有人广播,或在两者之间的任何位置广播)

The end result is that the Hub receives changes/calls, calls the API, the API verifies the data and returns it back to the Hub. 最终结果是集线器接收更改/调用,调用API,API验证数据并将其返回到集线器。 The Hub can then update all clients. 然后,Hub可以更新所有客户端。 You then successfully have real-time database changes and real-time client changes. 然后,您可以成功进行实时数据库更改和实时客户端更改。 The only catch is any change outside of this system will require the clients to refresh, which means all client calls, especially changes, must go through Hubs. 唯一的问题是,此系统之外的任何更改都需要客户端刷新,这意味着所有客户端调用(尤其是更改) 必须通过集线器。

If you need more examples, I would be happy to show some. 如果您需要更多示例,我很乐意展示一些。 Obviously, security measures should be taken, and the code here is obviously only a small example. 显然,应该采取安全措施,这里的代码显然只是一个小例子。

If you want use signalr I think you should use push server .But you can use another way and send a request to the api and the api should know about db change. 如果你想使用信号器,我认为你应该使用推送服务器 。但是你可以使用另一种方式向api发送请求,api应该知道db更改。

For push server you can also see this . 对于推送服务器,您也可以看到这一点

There are some considerations that might help. 有一些考虑可能会有所帮助。

1- Since you are playing with the Access rights , so i would say that , you must check the access right at run time each time , user wants to access certain secured functionality , yes this will have some degradation in performance but ensure you the tighter granular security. 1-由于您正在使用访问权限,所以我想说,您必须在每次运行时检查访问权限,用户想要访问某些安全功能,是的,这会降低性能,但要确保您更紧凑粒度安全。

2- For sending periodic changes , i would say that , you can use Timer available in .Net and trigger changes at a certain interval. 2-对于发送定期更改,我想说的是,您可以使用.Net中可用的Timer并以一定间隔触发更改。

3- I still don't like the idea of sending security related information to the client (thin) because anybody with basic knowledge of JavaScript and Html can change the security by running your site in debug mode or through some automated tools like Fiddler. 3-我仍然不喜欢将安全相关信息发送给客户端的想法(瘦),因为任何具有JavaScript和HTML基本知识的人都可以通过以调试模式运行站点或通过诸如Fiddler之类的自动化工具来更改安全性。

I've made a library that proxies between a server side eventaggregator / service bus. 我创建了一个代理服务器端eventaggregator / service总线的库。 It makes it alot easier to stream line the events being sent to clients. 这使得简化发送给客户端的事件的流线化变得更加容易。 Take a look at the demo project here 在这里看看演示项目

https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/tree/master/SignalR.EventAggregatorProxy.Demo.MVC4 https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/tree/master/SignalR.EventAggregatorProxy.Demo.MVC4

Open the demo .sln and there is both a .NET client (WPF) and a javascript client example 打开演示.sln,同时有.NET客户端(WPF)和javascript客户端示例

Install using nuget 使用nuget安装

Install-Package SignalR.EventAggregatorProxy 

wiki 维基

https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/wiki https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/wiki

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM