Composite C1 Owin and SignalR integration

For my nuget package manager I am introducting two additional packages that will be avalible soon. I am using my nuget package manager to test them and when they work I will put them public.

What am I working on.

In the video you can see what this post is all about. Using SignalR and KnockoutJs for creating a Composite C1 Console Application. Its a preview of two packages coming soon. I am using them for my Nuget Package Manager.

Components

We will take a look at it from the developer perspective of what it would take to create a Composite C1 Console Application when the packages are released. We will use two components. We are using features from the 4.1 C1 release for creting razor views. Disclamer, its work in progress and you will see some inline styles and the UI is not at all pritty at this point.

@{
    Layout = "/Composite/controls/razor/RazorLayout.cshtml";
}


@section HtmlHead {
    <script type="text/javascript" src="knockout-3.0.0.js"></script>
    <script type="text/javascript" src="NugetPackageManagerPageBinding.js"></script>
}

<ui:page label="Nuget Package Manager" binding="NugetPackageManagerPageBinding">
    <ui:splitbox id="splitbox_ajax_id" orient="horizontal" layout="1:3:1">
        <ui:splitpanel>

        </ui:splitpanel>
        <ui:splitpanel>
            <ui:scrollbox forcefitness="true">
                <div id="search-busy" style="width:100%;height:100%;position:absolute;background:rgba(0, 0, 0, 0.30);color:white;display:none;">
                    <div style="font-size:30pt;width:250px;height:50px; position:absolute;left:50%;top:50%;margin-left:-125px;margin-top:-25px;">
                        Searching...
                    </div>
                </div>
                <div data-bind="foreach : packages">
                    <div style="height:53px;border:solid 1px black; overflow:hidden;" data-bind="click:$root.selectPackage">
                        <div style="float:left;width:8%;">
                            <img style="width:50px;padding:5px;" data-bind="attr:{src:IconUrl}" />
                        </div>
                        <div style="float:left;padding-left:5px;position:relative;width:92%;">
                            <strong data-bind="text:Title"></strong>
                            <div data-bind="text:Description" style="padding-top:5px;">

                            </div>

                        </div>

                    </div>

                </div>
            </ui:scrollbox>

            <div style="height:60px;">
                <input type="button" data-bind="click:previous" value="previous" style="cursor:pointer" />
               <span data-bind="text:currentPageIndex()+1"></span>
               <input type="button" data-bind="click:next" value="next" style="cursor:pointer" />
            </div>
        </ui:splitpanel>
        <ui:splitpanel style="padding-bottom:5px;">
            <div>
                <ui:datainput id="CommandInput" type="text" />
            </div>

            <!-- ko : with:package-->

            <ui:scrollbox style="padding:5px;">
                <div>
                    <strong>Download Count:</strong>
                    <span data-bind="text:DownloadCount" style="padding-top:5px;">
                    </span>
                </div>
                <div style="overflow:hidden;">

                    <strong>License:</strong>
                    <a href="#" target="_blank" data-bind="attr:{href:LicenseUrl}" 
                       style="padding-top: 5px;text-decoration:underline; cursor:pointer; ">
                        <span data-bind="text:LicenseUrl"></span>
                    </a>
                </div>
                <div>
                    <strong>Description:</strong>
                    <span data-bind="text:Description" style="padding-top:5px;">
                    </span>
                </div>


            </ui:scrollbox>

            <div style="height:20px;">
                <ui:button  style="float: right;">Install</ui:button>
            </div>
            <!-- /ko -->

        </ui:splitpanel>


    </ui:splitbox>

</ui:page>

NugetPackageManager.cshtml is the view that gets opened inside the console by a ActionExecutor. What you want to notice is the little knockout databinding and the page binding. 

NugetPackageManagerPageBinding.prototype = new SignalRPageBinding;
NugetPackageManagerPageBinding.prototype.constructor = NugetPackageManagerPageBinding;
NugetPackageManagerPageBinding.superclass = SignalRPageBinding.prototype;

/** * @class */
function NugetPackageManagerPageBinding() {

    /**     * @type {SystemLogger}     */
    this.logger = SystemLogger.getLogger("NugetPackageManagerPageBinding");


    this._tree = null;
    this._seachhover = null;

    this.packages = ko.observableArray([]);
    this.package = ko.observable();
    this.currentPageIndex = ko.observable(0);



    /*     * Returnable.     */
    return this;
}

/** * Identifies binding. */
NugetPackageManagerPageBinding.prototype.toString = function () {

    return "[NugetPackageManagerPageBinding]";
}
/**
* Setup update listeners and handle potential errors.
* @overloads {PageBinding#onBeforePageInitialize}
*/
NugetPackageManagerPageBinding.prototype.onBeforePageInitialize = function () {

    NugetPackageManagerPageBinding.superclass.onBeforePageInitialize.call(this);

    DOMEvents.addEventListener(this.bindingWindow.bindingMap.CommandInput.shadowTree.input, DOMEvents.KEYDOWN, this);
    this._seachhover = this.bindingWindow.document.getElementById('search-busy');

  
}
NugetPackageManagerPageBinding.prototype.onAfterPageInitialize = function () {

    NugetPackageManagerPageBinding.superclass.onAfterPageInitialize.call(this);

    var self=this;
    ko.computed(function () {
        self.search(self.bindingWindow.bindingMap.CommandInput.getValue(), self.currentPageIndex());
    }).extend({throttle:1});

    ko.applyBindings(this);
}

	
/**
 * @implements {IEventListener}
 * @overloads {Binding#handleEvent}
 * @param {Event} e
 */
NugetPackageManagerPageBinding.prototype.handleEvent = function (e) {

    NugetPackageManagerPageBinding.superclass.handleEvent.call(this, e);

    if (e.type === DOMEvents.KEYDOWN && e.keyCode === KeyEventCodes.VK_ENTER)
    {
        this.currentPageIndex(null);
        this.currentPageIndex(0);
        
    }

  
}

NugetPackageManagerPageBinding.prototype.selectPackage = function(pac)
{
    this.package(pac);
}
NugetPackageManagerPageBinding.prototype.next =function()
{
    this.currentPageIndex(this.currentPageIndex() +1);
}
NugetPackageManagerPageBinding.prototype.previous = function()
{
    this.currentPageIndex(this.currentPageIndex() - 1);
}
NugetPackageManagerPageBinding.prototype.search = function(text, page)
{
    var _this = this;

    this._hub.invoke('search',
        text,
        false,
      page, 10
    ).done(function (data) {
        console.log(data);
        _this.packages(data);
        _this._seachhover.style.display = "none";
    }).fail(function (err) {
        console.log(arguments);
        _this._seachhover.style.display = "none";
    });


    this._seachhover.style.display = "block";

}

In the SignalRPageBinding the connection and subscribtion of hubs will take place, so you as a developer simple can invoke(...) methods or subscribe with on(...) for messages like normal in signalr when not using the proxy script. Notice how I just do this.hub.invoke('search',arguments) and get a promish back that will give me the data when the server is done fething it from Nuget.

The last part is then the SignalR Hub

namespace Composite.Integration.Nuget.Hubs
{

    [C1ConsoleHub]
    public class PackageManagerHub : Hub
    {

        private static readonly CompositePackageManager _nugetPackageManger = new CompositePackageManager();

        public PackageManagerHub()
        {
        }

        public Task<IEnumerable<object>> Search(string searchterm,bool pre = false, int page = 0, int take = 10)
        {
            return Task.Run<IEnumerable<object>>(() =>
            {
                DateTime now = DateTime.Now;

                var results = _nugetPackageManger.SourceRepository.Search(searchterm,pre)
                    .Where(p => p.IsLatestVersion)
                    .OrderByDescending(p => p.DownloadCount)
                    .Skip(page * take).Take(take)
                    .AsEnumerable().Where(PackageExtensions.IsListed)           
                    .ToList();

                return results.Select(p => new { p.Id, p.Description, p.DownloadCount, p.IconUrl, p.Title, p.LicenseUrl, p.Tags, p.Version,p.Published });
            });            
        }

    }
}

The Attribute on the Hub makes sure that its only useable from console users, and if a developer want to enforce further restriction on what methods can be called from console users, he will have to pass the entitytoken along the method signature and check for permissions in the hub methods. The goal here is to make it easy for people outside the composite c1 ecosystem to at ease and fast create C1 Applications. Hubs will make it easy to send data back and forward between server and client. When this integration is stable I plan to create a little "whos online" feature in the console for chatting with other editors. 

OWIN - Open Web Interface for .Net

SignalR depends on Owin and therefore it needs to be handled also. The Owin integration will be a way to register owin middleware from modules and have C1 put it all together in its startup process. Right now I decided to just use the ApplicationStartupAttribute that composite already use for startup things and if a AppBuilder configure is present it will be integrated.

    [ApplicationStartup]
    public class Bootstrap 
    {
        public void Configuration(IAppBuilder app)
        {
            Log.LogInformation(typeof(Bootstrap).Assembly.FullName, "Owin Integration");
            app.MapSignalR("/c1-signalr", new HubConfiguration() { EnableDetailedErrors = true });
            
        }

        public static void OnBeforeInitialize()
        {
            Log.LogInformation(typeof(Bootstrap).Assembly.FullName, "About to get initialized");
        }

        public static void OnInitialized()
        {
            Log.LogInformation(typeof(Bootstrap).Assembly.FullName, "Already initialized");
        }
    }

Conclussion

So hopefully more peolpe from the .Net developing platform will consider using Composite C1 when seeing how easy they can get things done in this amazing system. As always, I love playing around and from playing I hope to find new ways of doing things - and maybe the new way is more preferable then the old. Leave a comment if you have a request or a comment for what direction to shape this. 



comments powered by Disqus