Step 1
First of all, in the New Project dialog, select Installed > Visual C# > Web > ASP.NET Core Web Application. We will use nuGet named Coravel instead of Task<> object for queuing. Shortly, lets install this nuGet first.

Step 2
To add the SignalR client library

  • Right click on the project and select Add > Client-Side Library in Solution Explorer
  • For Provider, select unpkg in the Add Client-Side Library dialog
  • For Library, pick out @aspnet/signalr@1, and select the latest version
  • Select Choose specific files, go under the dist/browser folder and select both signalr.js and signalr.min.js
  • Set Target Location to wwwroot/lib/signalr/ and select Install

Step 3
Afterwards, we create a view whose name is Index.cshtml

@{
    ViewData["Title"] = "Home Page";
}

<div id="response"></div>

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>

@*PROGRESS BAR MODAL*@
<div class="modal fade in" id="mod-progress" data-backdrop="static" data-keyboard="false" tabindex="-1" role="dialog">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
            </div>
            <div class="modal-body">
                <div class="text-center">
                    <div class="i-circle warning"><i class="fa fa-info"></i></div>
                    <div id="result" style="font-size: 1.1em; padding-bottom: 5px">
                        <p id="progressBarParagraph"></p>
                    </div>
                    <div style="width: 30%; margin: 0 auto; display:none;">
                        <div id="progressbar" style="width: 300px; height: 15px"></div>
                        <br />
                    </div>
                    <div id="ProgressStripe" class="progress progress-striped active" style="position:relative; top:10px; width:100%;">
                        <div id="ProgressMessage" class="progress-bar progress-bar-info" style="width: 100%; border:none;"></div>
                    </div>
                    <br />
                </div>
            </div>
        </div>
        <!-- /.modal-content -->
    </div>
    <!-- /.modal-dialog -->
</div>
<!-- /.modal -->
@*Progress Bar*@
<div class="jumbotron" style="background-color:white;">
    <h1>ASP.NET</h1>
    <p class="lead">SignalR ProcessBar Simple Example</p>
    <button id="progressButton" type="button" class="btn btn-primary btn-success">Start the progress bar</button>
</div>
@*Progress Bar*@

@*Message Board*@
<div class="container">
    <div class="row">
        <div class="col-12">
            User <input type="text" id="userInput" />
            <br /><br />
            Message <input type="text" id="messageInput" />
            <input type="button" id="sendButton" value="Send Message" />
        </div>
    </div>
    <div class="row">
        <div class="col-12">
            <hr />
        </div>
    </div>
    <div class="row">
        <div class="col-6"> </div>
        <div class="col-6">
            <ul id="messagesList"></ul>
        </div>
    </div>
</div>

<p>Status of your background job: <strong><span id="job-status">Job status will go here...</span></strong></p>
@*Message Board*@

@*Progress Bar*@
<form asp-action="StartProgress">
    <button class="btn btn-primary btn-lg">Queue Background Job with Progress Bar</button>
</form>
@*Progress Bar*@
<br />
<form>
    <button id="getprog" class="btn btn-primary btn-lg">Queue Background Job with Progress Bar - Ajax Call</button>
</form>

<script src="~/lib/signalr/dist/browser/signalr.js"></script>
<script src="~/js/Helper.js" type="text/javascript"></script>

<script>
    var connectionId;

    var connection = new signalR.HubConnectionBuilder().withUrl("/ProgressHub").build();

    document.getElementById("progressButton").addEventListener("click", function (event) {

        connection.invoke("SendProgress", "Process in first progress...", connectionId).catch(function (err) {
            return console.error(err.toString());
        });

        event.preventDefault();
    });

    //Disable send button until connection is established
    document.getElementById("sendButton").disabled = true;

    connection.on("ReceiveMessage", function (user, message) {
        var msg = message.replace(/&amp;/g, "&amp;").replace(/</g, "<").replace(/>/g, ">");
        var encodedMsg = user + " says " + msg;
        var li = document.createElement("li");
        li.textContent = encodedMsg;
        document.getElementById("messagesList").appendChild(li);
    });

    connection.on("AddProgress",
        (message, percentage) => {
            ProgressBarModal("show", message + " " + percentage);
            $('#ProgressMessage').width(percentage);
            if (percentage == "100%") {
                ProgressBarModal();
            }
        });

    document.getElementById("sendButton").addEventListener("click", function (event) {

        var user = document.getElementById("userInput").value;
        var message = document.getElementById("messageInput").value;

        connection.invoke("SendMessage", user, message, connectionId).catch(function (err) {
            return console.error(err.toString());
        });

        event.preventDefault();
    });

    connection.on("progress",
        (percent) => {
            if (percent === 100) {
                document.getElementById("job-status").innerText = "Finished!";
            } else {
                document.getElementById("job-status").innerText = percent + "%";
            }
        });

    $(document).on("click", "#getprog", function (event) {

        //var data1 = new FormData();
        //data1.append("connectionId", connectionId);

        $.ajax({
            url: 'https://localhost:5001/Home/getProgress',
            type: 'get',
            //type: 'post',
            contentType: 'text/html',
            data: { "connectionId": connectionId },
            //data: data1,
            success: function (data) {
                $('#response').text(data);
                //connection.stop();
            }
        });

        event.preventDefault();

    });

    connection.start().then(function () {
        document.getElementById("sendButton").disabled = false;
        console.log('Connection Started');
        // Send the connectionId to controller
        connection.invoke('getConnectionId')
            .then(connectionId => {
                this.connectionId = connectionId;
            });
    }).catch(function (err) {
        return console.error(err.toString());
        connection.stop();
    });

</script>

Step 4
Second view will be Progress.cshtml to create

@{
    ViewData["Title"] = "Progress";
}

<h1>Progress</h1>

@*PROGRESS BAR MODAL*@
<div class="modal fade in" id="mod-progress" data-backdrop="static" data-keyboard="false" tabindex="-1" role="dialog">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
            </div>
            <div class="modal-body">
                <div class="text-center">
                    <div class="i-circle warning"><i class="fa fa-info"></i></div>
                    <div id="result" style="font-size: 1.1em; padding-bottom: 5px">
                        <p id="progressBarParagraph"></p>
                    </div>
                    <div style="width: 30%; margin: 0 auto; display:none;">
                        <div id="progressbar" style="width: 300px; height: 15px"></div>
                        <br />
                    </div>
                    <div id="ProgressStripe" class="progress progress-striped active" style="position:relative; top:10px; width:100%;">
                        <div id="ProgressMessage" class="progress-bar progress-bar-info" style="width: 100%; border:none;"></div>
                    </div>
                    <br />
                </div>
            </div>
        </div>
        <!-- /.modal-content -->
    </div>
    <!-- /.modal-dialog -->
</div>
<!-- /.modal -->

<p>Status of your background job: <strong><span id="job-status">Job status will go here...</span></strong></p>

<script src="~/lib/signalr/dist/browser/signalr.js"></script>
<script>
        var connection = new signalR.HubConnectionBuilder()
            .withUrl("/ProgressHub")
            .configureLogging(signalR.LogLevel.Information)
        .build();

        connection.on("progress",
            (percent) => {
                if (percent === 100) {
                    document.getElementById("job-status").innerText = "Finished!";
                } else {
                    document.getElementById("job-status").innerText = percent + "%";
                }
        });

        connection.on("AddProgress",
            (message, percentage) => {
                ProgressBarModal("show", message + " " + percentage);
                $('#ProgressMessage').width(percentage);
                if (percentage == "100%") {
                    ProgressBarModal();
                }
        });
     
        connection.start()
            .then(_ => connection.invoke("AssociateJob", "@ViewBag.JobId"))
            .catch(err => console.error(err.toString()));
</script>

<script src="~/js/Helper.js" type="text/javascript"></script>

Step 5
Time to create a controller whose name is HomeController.cs as default

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Coravel.Queuing.Interfaces;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using SignalR.Hubs;
using SignalR.Models;

namespace SignalR.Controllers
{
    public class HomeController : Controller
    {
        private IHubContext<ProgressHub> HubContext { get; set; }
        private readonly IQueue _queue;

        public HomeController(IHubContext<ProgressHub> hubcontext, IQueue queue)
        {
            HubContext = hubcontext;
            _queue = queue;
        }

        public IActionResult Index()
        {
            _queue.QueueAsyncTask(async () =>
            {
                for (int i = 0; i <= 100; i += 5)
                {
                    Debug.WriteLine($"Background job progress: {i}");

                    await Task.Delay(100);
                }
            });

            return View();
        }

        public async Task<JsonResult> LongRunningProcess(string connectionId)
        {
            //THIS COULD BE SOME LIST OF DATA

            ProgressHub functions = new ProgressHub();

            await functions.SendProgress("Process in progress...", connectionId);

            return Json("OK");
        }

        public string getProgress(string connectionId)
        {
            _queue.QueueAsyncTask(async () =>
            {
                int itemsCount = 5;

                for (int i = 0; i <= itemsCount; i++)
                {
                    //SIMULATING SOME TASK
                    Thread.Sleep(500);

                    var percentage = (i * 100) / itemsCount;
                     
                    await HubContext.Clients.Client(connectionId).SendAsync("AddProgress", "Uploading...", percentage + "%");
                }
            });

            return "Done after response";
        }

        [HttpPost]
        public IActionResult StartProgress()
        {
            string jobId = Guid.NewGuid().ToString("N");
            _queue.QueueAsyncTask(() => PerformBackgroundJob(jobId));

            return RedirectToAction("Progress", new { jobId });
        }

        public IActionResult Progress(string jobId)
        {
            ViewBag.JobId = jobId;

            return View();
        }

        private async Task PerformBackgroundJob(string jobId)
        {
            int total = 100;

            for (int i = 0; i <= total; i += 5)
            {
                // TODO: report progress with SignalR
                var percentage = (i * 100) / total;

                await HubContext.Clients.Group(jobId).SendAsync("progress", percentage);

                await HubContext.Clients.Group(jobId).SendAsync("AddProgress", "Uploading...", percentage + "%");
                
                await Task.Delay(100);
            }
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

Step 6
When it comes to hub system , we should create a separate class named ProgressHub.cs

using Microsoft.AspNetCore.SignalR;
using System.Threading;
using System.Threading.Tasks;

namespace SignalR.Hubs
{
    public class ProgressHub : Hub
    {
        public async Task SendProgress(string progressMessage, string connectionId)
        {
            int itemsCount = 5;

            for (int i = 0; i <= itemsCount; i++)
            {
                //SIMULATING SOME TASK
                Thread.Sleep(500);

                var percentage = (i * 100) / itemsCount;

                //CALLING A FUNCTION THAT CALCULATES PERCENTAGE AND SENDS THE DATA TO THE CLIENT
                //await Clients.Client(Context.ConnectionId).SendAsync("AddProgress", progressMessage, percentage + "%");
                await Clients.Client(connectionId).SendAsync("AddProgress", progressMessage, percentage + "%");
            }
        }

        public async Task SendMessage(string user, string message, string connectionId)
        {
            //await Clients.All.SendAsync("ReceiveMessage", user, message);
            //await Clients.Caller.SendAsync("ReceiveMessage", user, message);

            //await Clients.Client(Context.ConnectionId).SendAsync("ReceiveMessage", user, message);
            await Clients.Client(connectionId).SendAsync("ReceiveMessage", user, message);

            int itemsCount = 5;

            for (int i = 0; i <= itemsCount; i++)
            {
                //SIMULATING SOME TASK
                Thread.Sleep(500);

                //CALLING A FUNCTION THAT CALCULATES PERCENTAGE AND SENDS THE DATA TO THE CLIENT
                await Clients.Client(Context.ConnectionId).SendAsync("progress", i);
            }
        }
         
        public async Task AssociateJob(string jobId)
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, jobId);
        }

        public string GetConnectionId()
        {
            return Context.ConnectionId;
        }
    }
}

Step 7
Last thing is setting up the Startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Coravel;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using SignalR.Hubs;

namespace SignalR
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // 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;
            });

            services.AddSession(options =>
            {
                options.IdleTimeout = TimeSpan.FromSeconds(1200);
            });

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

            services.AddQueue();
 
            services.AddSignalR();

        }

        public static ConnectionManager ConnectionManager;

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            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.UseSignalR(routes =>
            {
                routes.MapHub<ProgressHub>("/ProgressHub");
            });

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

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *