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

时间:2013-09-27 06:13:00

标签: c# asp.net asp.net-mvc sql-server-2008-r2 signalr

好吧,我跟着聊天应用程序演示就好了,还有其他一些东西,但是所有这些都没有回答,或者由于我缺乏智能来得到我想要的答案。方案如下

我正在建立一个

的网站
  1. MVC 4
  2. .NET Framework 4.0
  3. IIS 7.5
  4. ASP.NET 4
  5. SQL Server 2008 R2
  6. 这是伴随桌面应用程序,即我的Web应用程序和桌面应用程序将与同一个DB进行交互。我想在网站和桌面应用程序中添加AMS(访问管理系统),以便用户可以对应用程序内部任何功能的访问权限进行精细管理。我希望实时将更改推送给wesite。

    例如:来自桌面应用程序的经理撤销了职员查看销售报告的权利,并且有几个职员在网站上在线,因此我希望这些更改在网站制作时被推送到网站。

    现在我正在登录时存储用户的权限,因此随着用户数量的增加不会导致网站的性能迅速降低,现在我知道在检查是否允许的每个操作时,我可以往返DB并检查条件,但正如我之前所说,这会降低性能。

    现在我想推断网站用户的权利,如果网站本身或桌面应用程序发生了一些变化,我查看了SignalR,我在SO上找到了解决方案({{3} }和This),但我不希望时钟自动收报机连续运行,然后将更改广播到所有连接的客户端。并且正在更改的用户可能连接到网站或者可能没有。请有人指出我正确的方向

4 个答案:

答案 0 :(得分:4)

我花了很多时间尝试为此找到解决方案,而我发现使用SignalR最简单的方法是使用Hub作为Repository / API的网关:

所以,这是项目的设置方式:

  1. ASP.NET MVC控制器的操作会整个页面。

    public class HomeController : Controller
    {
            //
            // GET: /Home/
            public ActionResult Index()
            {
                  return View();
            }
    }
    
  2. View应该包含在一个加载Knockout MVVM的Layout中。 View然后初始化需要使用的MVVM部分(例如,将所有MVVM脚本集中在一个文件中,并且View初始化SignalR连接,以避免不必要的连接(下面的代码让脚本初始化)) 。 View还附加了KnockOut绑定。

  3. 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);
                }
             }
          }
    }
    

    运行脚本(放置在布局,脚本文件或视图中,具体取决于每页的需求或确认)

    $(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!');
        });;
    });
    

    布局:

    <!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>
    

    查看(索引):

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

    _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. KnockOut脚本与SignalR Hub交互。 Hub接收呼叫,在必要时检查授权,并将呼叫传递到适当的存储库或直接传递给WebAPI 2(此示例)。然后,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. 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. 您的.NET应用程序需要使用与javascript-ran网站相同的功能。这将允许来自任何客户端的修改然后传播到需要的许多客户端(刚刚加载的单个客户端,如在此示例中,或者向每个人广播,或者在其间的任何地方)
    4. 最终结果是Hub接收更改/调用,调用API,API验证数据并将其返回到Hub。然后,Hub可以更新所有客户端。然后,您可以成功进行实时数据库更改和实时客户端更改。唯一的问题是,此系统之外的任何更改都需要客户端刷新,这意味着所有客户端呼叫,尤其是更改,必须通过集线器。

      如果您需要更多示例,我很乐意展示一些。显然,应该采取安全措施,这里的代码显然只是一个小例子。

答案 1 :(得分:1)

如果你想使用signalr,我认为你应该使用push server。但你可以使用另一种方式向api发送请求,api应该知道db更改。

对于推送服务器,您还可以看到this

答案 2 :(得分:1)

有些考虑可能有所帮助。

1-由于您正在使用访问权限,所以我想说,您必须在每次运行时检查访问权限,用户想要访问某些安全功能,是的,这会降低性能,但要确保你更细致的安全性。

2-对于发送定期更改,我会说,你可以使用.Net中的Timer,并以一定的间隔触发更改。

3-我仍然不喜欢向客户端发送安全相关信息(瘦)的想法,因为任何具有JavaScript和Html基础知识的人都可以通过在调试模式下运行您的站点或通过某些自动化工具来改变安全性的Fiddler。

答案 3 :(得分:1)

我已经创建了一个代理服务器端eventaggregator / service总线的库。它使得对发送给客户端的事件进行流线化变得更加容易。看看这里的演示项目

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

打开演示.sln,有一个.NET客户端(WPF)和一个javascript客户端示例

使用nuget安装

Install-Package SignalR.EventAggregatorProxy 

维基

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