MVC4 & SignalR Only executing in other windows - asp.net-mvc-4

I am doing an MVC4 web application and decided using signalr, there is just one thing I don't understand, I'm probably doing something wrong...
I have a page (Home) with an ActionLink that call the HomeController.
In the controller I have this:
HomeController:
public class HomeController : Controller
{
private IHubContext context;
public HomeController()
{
context = GlobalHost.ConnectionManager.GetHubContext<HomeHub>();
}
...
public ActionResult Scan()
{
context.Clients.All.StartedScanning();
}
}
HomeHub:
public class HomeHub : Hub
{
public void StartedScanning()
{
Clients.All.setStatusMessage("Scanning started...");
}
}
Index.cshtml
...
#Html.ActionLink("Scan for media", "scan", null, new
{
#class = "scanActionLink"
})
...
#section scripts
{
<script src="~/Scripts/jquery.signalR-1.1.2.js"></script>
<script src="~/signalr/hubs"></script>
<script type="text/javascript">
$(function () {
var home = $.connection.homeHub;
home.client.setStatusMessage = function (message) {
alert(message);
}
// Start the connection.
$.connection.hub.start().done(function () {
});
})
</script>
}
Actually the code is executing all right, but only in other windows and not on the main. Is that normal?
I hope I've been clear enough.
Thanks.

By clicking your ActionLink, you are reloading the page. SignalR is not yet (re)loaded in the context of the new page, so the notification will not be sent to it by the server. You may want to try the StartedScanning request as an AJAX request rather than navigating away from the current page.

Related

Blazor WASM ViewModel

I did a lot of Razor pages the past year, and a couple of weeks ago I started to transform all to a ViewModel for my Blazor Server App.
Now I thought it's time to make a new Blazor WebAssembly App.
But I struggle to build a POC with a ViewModel, based on the WeatherForecast example.
But whatever I do, I have errors. And so far I did not find a a good basic example.
Unhandled exception rendering component: Unable to resolve service for type 'fm2.Client.Models.IFetchDataModel' while attempting to activate 'fm2.Client.ViewModels.FetchDataViewModel'.
System.InvalidOperationException: Unable to resolve service for type 'fm2.Client.Models.IFetchDataModel' while attempting to activate 'fm2.Client.ViewModels.FetchDataViewModel'.
Example: https://github.com/rmoergeli/fm2
namespace fm2.Client.ViewModels
{
public interface IFetchDataViewModel
{
WeatherForecast[] WeatherForecasts { get; set; }
Task RetrieveForecastsAsync();
Task OnInitializedAsync();
}
public class FetchDataViewModel : IFetchDataViewModel
{
private WeatherForecast[] _weatherForecasts;
private IFetchDataModel _fetchDataModel;
public WeatherForecast[] WeatherForecasts
{
get => _weatherForecasts;
set => _weatherForecasts = value;
}
public FetchDataViewModel(IFetchDataModel fetchDataModel)
{
Console.WriteLine("FetchDataViewModel Constructor Executing");
_fetchDataModel = fetchDataModel;
}
public async Task RetrieveForecastsAsync()
{
_weatherForecasts = await _fetchDataModel.RetrieveForecastsAsync();
Console.WriteLine("FetchDataViewModel Forecasts Retrieved");
}
public async Task OnInitializedAsync()
{
_weatherForecasts = await _fetchDataModel.RetrieveForecastsAsync();
}
}
}
namespace fm2.Client
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<IFetchDataViewModel, FetchDataViewModel>();
await builder.Build().RunAsync();
}
}
}
Additional note:
Here how I did it previously for Blazor Server App: https://github.com/rmoergeli/fm2_server
Here I try the same for the Blazor WebAssembly App:
https://github.com/rmoergeli/fm2_wasm (Constructor is not initialized).
This POC is different comapred to the first link at the top. Here I tried to just do the same like I did for the Blazor Server App.
I pulled the latest code from Github. It looks like the wrong api was getting called.
When I changed from this:
WeatherForecast[] _weatherForecast = await _http.GetFromJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
to this:
WeatherForecast[] _weatherForecast = await _http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
in WeatherViewModel.cs
I could get the weather data to be displayed.

How to properly refresh a PartialView in Razer page .Net Core 3.1

I have a simple page that also includes a PartialView so that I can refresh the data every 10 seconds returned by the PartialView. It really is a trivial task but I'm not proficient enough with the whole web part and especially JavaScript.
How can I run the LoadData() Method every X seconds so that my partialView shows the data?
I have found the following JS code which I think should be able to refresh the PartialView but I don't know how to adapt this to my situation:
<script type="text/javascript">
$(function () {
setInterval(loadTable, 1000); // invoke load every second
loadTable(); // load on initial page loaded
});
function loadTable() {
$('#data').load('/controller/tabledata');
}
</script>
My index.cshtml looks like this:
#page
#model IndexModel
<div class="data">
<partial name="~/Pages/Shared/_BezoekersData.cshtml" />
</div>
The indexModel:
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.SharePoint.Client;
using PnP.Framework;
using System.Security;
namespace MyNameSpace.Pages
{
public class IndexModel : PageModel
{
public IActionResult OnGetPartial() => Partial("\\Shared\\_BezoekersData.cshtml");
// properties left out for clarity
public void OnGet()
{
LoadData();
}
public void LoadData()
{
// dataRetrieval part here.
}
}
}
_BezoekersData.cshtml:
#{
ViewData["Title"] = "Home page";
}
<div style="position:fixed; left:65px; top:25px; color:#c2a149; font-size:75px; ">#Model.Vestiging</div>
<div style="position:fixed; left:1100px; top:180px; color:#c2a149; font-size:50px">#Model.MaxAantal</div>
<div style="position:fixed; left:1100px; top:285px; color:#c2a149; font-size:50px">#Model.HuidigAantal</div>
You should add one more function to call the partial view and refresh it, check this:
public void OnGet()
{
LoadData();
}
public void LoadData()
{
MyModel=new MyModel {thetime=DateTime.Now };
}
public PartialViewResult OnGetMyPartial() // extra method to get data
{
MyModel = new MyModel { thetime = DateTime.Now };
return Partial("_BezoekersData", MyModel);
}
View:
Mind it must be ** div id="data"**, because you use #data to call jquery function.
Pass model data to partial, add model attribute.
And to call the new method, using handler:
In main view: #Model.MyModel.thetime
<div id="data">
<partial name="~/Pages/Shared/_BezoekersData.cshtml" model="Model.MyModel"/> //add a model attribute
</div>
#section Scripts
{
<script type="text/javascript">
$(function () {
setInterval(loadTable, 1000);
loadTable();
});
function loadTable() {
$('#data').load("/Index?handler=MyPartial"); //handler to call razor method
}
</script>
}
This is the partial view:
#model He621.Models.MyModel
In partial:<div style="position:fixed; left:65px; top:100px; color:#c2a149; font-size:75px; ">#Model.thetime</div>
Result:
The time refreshes every 1 seconds, and will only refresh the partial:

SignalR context within core3.1 controller - no context.clients

I am trying to call a SignalR Hub Action from a controller.
On my controller I have this:
private readonly IHubContext<TurnHub> _hubContext;
public HomeController(ILogger<HomeController> logger, IHubContext<TurnHub> hubContext)
{
_logger = logger;
_hubContext = hubContext;
_gameService = new GameService(ModelState);
}
public async Task<IActionResult> Test()
{
return View();
}
public async Task<IActionResult> TestMessage()
{
await _hubContext.Clients.All.SendAsync("TurnChanged", 1);
return View();
}
When I break on hub context, I can see nodes for "Clients" and "Groups" but there are no clients or groups under that level. Running the controller action sees no errors, but the message isn't pushed to the client.
On the hub I have this:
public class TurnHub : Hub
{
public async Task EndTurn(int nextUser)
{
await Clients.All.SendAsync("TurnChanged", nextUser);
}
}
And the view has this:
<script>
var connection = new signalR.HubConnectionBuilder().withUrl("/TurnHub").build();
connection.on("TurnChanged", function (nextUser) {
debugger;
alert(nextUser);
});
</script>
I was expecting any browser window that was displaying that view to alert when one of the clients hits that controller action (Called from a button on that view).
What am I doing wrong?
I have the signalr core package installed, the js file from "add client library" #microsoft/signalr. There are no console errors on the browser to say anything is wrong!
Any help greatly appreciated.
In your javascript client you need to start the hub connection. Like this
connection.start();
The start will return a promise so you could do some stuff after the hub has been connected. Also failures in connection can be tracked by catching errors on that promise.

Tag helpers not working with DynamicRouteValueTransformer

ASP.NET Core's tag helpers appear not to be working in combination with DynamicRouteValueTransformer.
Here's an MCVE: let's say we have a simple controller with two actions...
public class GenericController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult Add()
{
return View();
}
}
... and the Index view links to the Add action with the asp-action tag helper:
<!DOCTYPE html>
<html>
<body>
<a asp-action="Add">Add new!</a>
</body>
</html>
When we now open /Generic in a browser and inspect the page, we'll notice that ASP.NET generated the expected href value for the controller action:
Add new!
So far, so good. Now let's create a DynamicRouteValueTransformer which routes all requests to that same controller...
public class Transformer : DynamicRouteValueTransformer
{
public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
if (values.ContainsKey("controller"))
{
values["originalController"] = values["controller"];
values["controller"] = "Generic";
}
return new ValueTask<RouteValueDictionary>(values);
}
}
... and let's set it up for endpoint routing in Startup.cs...
services.AddSingleton<Transformer>();
...
app.UseEndpoints(endpoints =>
{
endpoints.MapDynamicControllerRoute<Transformer>("{controller=Home}/{action=Index}/{id?}");
});
After opening /Generic (or any other route, e.g. /Foobar) in a browser and inspecting the page, we'll now notice that asp-action tag helper is no longer working:
Add new!
Since the href value is empty, the link is no longer working. It looks the dynamic routing broke the tag helper.
Any suggested fix or workaround?

Gracefully handling permalinks when using pushstate for partial view pagination

I'm looking for a clean way to handle permalinks when my application uses pushstate to load ASP.NET MVC partial views for pagination
If my controller returns a partial view (currently) then the permalink will display the partial view alone without the rest of the site defined by _Layout.cshtml.
If my controller return a regular view, the permalink works but loading the entire page into the container provided only for part of the page creates a type of Droste Effect.
The only solution I've found is to redirect the 404 to the homepage and navigate to correct page using the relevant portion of the url.
However, this prevents me from using my custom error page (which just happens to be an awesome error page).
Ok I figured out how to make this work with a double-controller setup.
NavigationController handles ajax calls and returns partial views (note there is no "Index")
public class NavigationController : Controller
{
public PartialViewResult Home()
{
return PartialView("Home", null);
}
public PartialViewResult About()
{
return PartialView("About", null);
}
...
}
Separate PermalinkController handles permalinks by returning entire regular Views (note this has an "Index" but no "Home":
public class PermalinkController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
return View();
}
...
}
Routes:
routes.MapRoute(
"PermalinkRouter",
"{action}",
new { controller = "Permalink", action = "Index" },
new { }
);
routes.MapRoute(
"NavigationRouter",
"Navigation/{action}/{id}",
new { controller = "Navigation", action = "Home", id = UrlParameter.Optional },
new { }
);
Views:
~/Views/Shared/_Layout.cshtml:
...
<header></header>
<div id="pageViewContainer">
#RenderBody()
</div>
<footer></footer>
...
~/Views/Navigation/Home.cshtml PartialView:
<div> Home Content </div>
~/Views/Permalink/Index.cshtml View:
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#Html.Partial("~/Views/Navigation/Home.cshtml", null)