2

好吧,我很好地遵循了聊天应用程序演示,还有其他一些 tuts,但是所有这些都没有回答,或者由于我缺乏智力来得出我想要的答案。场景如下

我正在建立一个网站

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

那是伴随一个桌面应用程序,即我的网络应用程序和桌面应用程序将与同一个数据库进行交互。我想在网站和桌面应用程序中添加一个 AMS(访问管理系统),以便可以细粒度地管理用户对应用程序内任何功能的访问权限。我希望将更改实时推送到网站。

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

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

现在,如果网站本身或桌面应用程序有一些更改,我想将用户的权利推送到网站,我查看了 SignalR,并在 SO(ThisThis)上找到了解决方案,但我不希望时钟连续运行,然后将更改广播到所有连接的客户端。而正在改变的用户,可能连接到网站,也可能没有。请有人指出我正确的方向

4

4 回答 4

4

我花了很多时间试图找到解决方案,我发现使用 SignalR 最简单的方法是使用 Hubs 作为您的存储库/API 的网关:

因此,以下是项目的设置方式:

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

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

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>

_地址:

<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 站点相同的功能。这将允许从任何客户端进行修改,然后传播到需要的多个客户端(如本例中刚刚加载的单个客户端,或广播给所有人,或介于两者之间的任何地方)

最终结果是 Hub 接收更改/调用,调用 API,API 验证数据并将其返回给 Hub。然后,集线器可以更新所有客户端。然后,您成功地进行了实时数据库更改和实时客户端更改。唯一的问题是该系统之外的任何更改都需要客户端刷新,这意味着所有客户端调用,尤其是更改,都必须通过集线器。

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

于 2013-12-07T06:46:05.253 回答
1

我制作了一个在服务器端 eventtaggregator / 服务总线之间代理的库。它使流线化发送给客户端的事件变得更加容易。看看这里的演示项目

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

于 2013-09-27T09:11:53.453 回答
1

如果您想使用信号器,我认为您应该使用推送服务器。但是您可以使用另一种方式并向 api 发送请求,并且 api 应该知道数据库更改。

对于推送服务器,您也可以看到这个

于 2013-09-27T06:19:18.750 回答
1

有一些考虑可能会有所帮助。

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

2-对于发送定期更改,我想说的是,您可以使用 .Net 中可用的 Timer 并以特定间隔触发更改。

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

于 2013-09-27T06:23:07.067 回答