SignalR and MVC bundle - asp.net-mvc

I'm trying to use SignalR with MVC bundle, but having problem finding out how to include the /signalr/hubs script into the bundle. For now I have to insert the path in between jquery.signalR and my code. That will result in three javascript file requests.
Is there any way to include /signalr/hubs into my mvc bundle?

A bit late, but here is my contribution:
Create a javascript file with the following contents:
(function ($) {
$.ajax({
url: "/signalr/hubs",
dataType: "script",
async: false
});
}(jQuery));
Then add the file to the bundles collection.
This will load the "/signalr/hubs" code for you.

The default /signalr/hubs script is generated dynamically by the runtime on the first request and then cached.
You can use hubify.exe (see http://weblogs.asp.net/davidfowler/archive/2012/06/10/signalr-0-5-1-released.aspx for details) to pre-generate the file yourself, so you can add it into the MVC bundle.

I know this is an old thread but I would like to add the following for SignalR 2.x. I really wanted to bundle the proxy using SquishIt and by trial and error I managed to come up with the following:
using Microsoft.AspNet.SignalR
using Microsoft.AspNet.SignalR.Hubs
var resolver = new DefaultHubManager(new DefaultDependencyResolver());
var proxy = new DefaultJavaScriptProxyGenerator(resolver, new NullJavaScriptMinifier());
string iCanHazScriptNao = proxy.GenerateProxy("/signalr");

From asp.net, using the SignalR.Utils NuGet package, I found that I needed to be in the directory with the DLL that has the hub in it:
(assuming you have a standard solution structure and are using 2.2.0 of SignalR.Utils)
cd C:\YourSolution\YourProjectWithTheHub\bin\Debug
..\..\..\packages\Microsoft.AspNet.SignalR.Utils.2.2.0\tools\signalr.exe ghp
After running the tool, there will be a server.js file in the directory you ran it from (in this case, Debug).
(Note: I couldn't get it to work when specifying the path with the /p flag, and for some reason even when it does work, it creates a temp directory with the signalr.exe file in it)

I used #KTW response mentioned on this Thread and here is the complete change
BundleConfig
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/modernizr-2.6.2.js",
"~/Scripts/jquery-2.2.3.js",
"~/Scripts/jquery-ui-1.11.4.js",
"~/Scripts/jquery.multiselect.js",
"~/Scripts/jquery.dataTables.js",
"~/Scripts/jquery.jstepper.min.js",
"~/Scripts/underscore.min.js"
));
bundles.Add(new ScriptBundle("~/bundles/SignalRScripts").Include(
"~/Scripts/jquery.signalR-2.2.2.min.js",
"~/Scripts/signalRBundle.js",
"~/Scripts/Views/Search/SignalRFunctions.js"));
}
}
SignalRFunctions.js
$(function() {
// Declare a proxy to reference the hub.
var usersHub = $.connection.currentUsersHub;
//Create a function that the hub can call to broadcast messages.
usersHub.client.broadcastMessage = function(reservationNumber, usrName) {
//Message broadcast from server
//now find the id with reservationNumber on the page and to that append the user name
var id = '#' + reservationNumber;
if ($(id).length) {
if (usrName.length) {
itemsOpened($(id), usrName);
} else {
itemsClosed($(id));
}
}
//else {
// //is it possible that server broad casted for a reservationNumber and is not available at the client?
//}
};
//Accepts dictionary from hub and goes through search results
//https://stackoverflow.com/questions/7776337/reading-c-sharp-dictionary-in-javascript
usersHub.client.broadcastCollection = function (reservationNumberAndUsers) {
for (var resNumKey in reservationNumberAndUsers) {
if (reservationNumberAndUsers.hasOwnProperty(resNumKey)) {
//Message broadcast from server
//now find the id with ReservationNumber on the page and to that append the user name
var id = '#' + resNumKey;
if ($(id).length) {
if (reservationNumberAndUsers[resNumKey].length) {
itemsOpened($(id), reservationNumberAndUsers[resNumKey]);
} else {
itemsClosed($(id));
}
}
}
}
};
$.connection.hub.start().done(function() {
var searchedReservationNumbers = [];
if (typeof jsData !== 'undefined') {
if (jsData && jsData.length) {
for (var i = 0; i < jsData.length; i++) {
searchedReservationNumbers.push(jsData[i].UReservationNumber);
}
if (searcheduReservationNumbers.length !== 0) {
usersHub.server.getWorkingUsersOnUReservationNumber(searcheduReservationNumbers);
}
}
}
}).fail(function () { console.log('Could not Connect To SignalrHub!'); });
/*In case we would decide to continuously reconnect making connection to server.
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
},
5000); // Restart connection after 5 seconds.
});*/
function itemsOpened(elem, id) {
var item = "Opened By - " + id;
elem.prop('title', item);
elem.css('background-color', 'chocolate');
};
function itemsClosed(elem) {
elem.prop('title', "");
elem.css('background-color', '');
};
});
signalRBundle.js
(function ($) {
$.ajax({
url: "/signalr/hubs",
dataType: "script",
async: false
});
}(jQuery));
/* Source https://stackoverflow.com/questions/11556110/signalr-and-mvc-bundle */
SomePartialView.cshtml
Instead of writing below in above partial view
#using Localization
#using Newtonsoft.Json
#model NameSpace.ViewModels.FilterVM
#{
ViewBag.Title = Strings.Filter;
}
#using (Html.BeginForm())
{
<div class="large-12 columns">
---SOME CODE HERE
</div>
}
#section scripts
{
<script type="text/javascript" language="javascript">
var jsData = #Html.Raw(JsonConvert.SerializeObject(Model));
</script>
<script src="~/Scripts/jquery.signalR-2.2.2.min.js"></script>
<script src="~/signalr/hubs"></script>
<script src="~/Scripts/Views/Search/SignalRFunctions.js"></script>
}
This changed to
#using Localization
#using Newtonsoft.Json
#model NameSpace.ViewModels.FilterVM
#{
ViewBag.Title = Strings.Filter;
}
#using (Html.BeginForm())
{
<div class="large-12 columns">
---SOME CODE HERE
</div>
}
#section scripts
{
<script type="text/javascript" language="javascript">
var jsData = #Html.Raw(JsonConvert.SerializeObject(Model));
</script>
#Scripts.Render("~/bundles/SignalRScripts")
}
Notice
#Scripts.Render("~/bundles/SignalRScripts")
in partial view above.Without #KTW file above(ajax request to /signalr/hubs)
var usersHub = $.connection.currentUsersHub;
was always coming as null.

You can generate the code with the Microsoft.AspNet.SignalR.Utils NuGet package. Add that to your project, then you can add the below as a post-build script (Project -> {project name} Properties -> Build Events). It should be a post-build script and not pre- since you want it to build against your updated hub code after it's compiled.
It will find whatever version of the Microsoft.AspNet.SignalR.Utils package that you have installed and put the server.js file in the Scripts folder.
You must also have a version redirect for Newtonsoft.Json in your web.config file (assuming your project uses Newtonsoft.Json). This is because signalr.exe is built against version 6.0.0 and you're likely using a newer version. The /configFile switch is to tell it to use your project's config file so that it uses the redirect.
Post-build script:
cd $(ProjectDir)\Scripts
FOR /F "usebackq delims=" %%p IN (`dir ..\..\packages /b /ad ^| find "Microsoft.AspNet.SignalR.Utils"`) DO (
set "UTILSPATH=%%p"
)
$(SolutionDir)\packages\%UTILSPATH%\tools\net40\signalr.exe ghp /path:$(TargetDir) /configFile:$(TargetPath).config
Then include ~/Scripts/server.js in your bundle.

Related

UmbracoApiController in Plugin Area Not Found

I've created an UmbracoApiController named FormDatasourceController that resides in my plugin area to be used in ajax calls to get or post data.
[PluginController("AreaName")]
public class FormDatasourceController : UmbracoApiController
{
[HttpGet]
public IEnumerable<ICountry> GetAllCountries()
{
return MerchelloConfiguration.Current.MerchelloCountries().Countries;
}
}
and in the cshtml file I have this request
<script type="text/javascript">
$(function(){
$('#sbmtBtn').on("click", function () {
$.get('~/Umbraco/AreaName/FormDatasource/GetAllCountries', null)
.success(function (data) {
alert(data);
})
.fail(function (e) {
alert('failed' + e);
});
});
});
However, I always get error status 404. I tried to EnableCors but with no luck.
Any advise? I'm using umbraco 7
Edit:
For AngularJs controllers/resources in Umbraco, just do this:
change
$.get('~/Umbraco/AreaName/FormDatasource/GetAllCountries', null)
to
$.get('AreaName/FormDatasource/GetAllCountries', null)
That should get your call working. Ignore the ~/Umbraco/ bit.
Your API call should look:
~/Umbraco/AreaName/FormDatasource/GetAllCountries
See "Plugin based controller" at https://our.umbraco.org/documentation/Reference/Routing/WebApi/#plugin-based-controller.
Note, if you had inherited from 'UmbracoAuthorizedApiController' or 'UmbracoAuthorizedJsonController' your api call should be something like this:
~/Umbraco/backoffice/AreaName/FormDatasource/GetAllCountries
For more information, take a look at the bottom of this page in the documentation site under Backoffice Controllers:
https://our.umbraco.org/documentation/Reference/Routing/WebApi/

Section Scripts or regular <script> depending on Request Type

I have a scenario where I would like to load scripts depending on the request type. For example, let's say I have this code:
#section scripts {
<script>
$('#tabsContainer').InitializeTabs();
</scripts>
}
this loads perfectly fine when I want to load the full page. However, I have a thing where user can load the page partially as well. The scenario is like this:
public ActionResult Page() {
if (Request.IsAjax()) return PartialView();
return View()
}
The problem is, if it's being loaded partially, the script does not work because #section scripts does not work in partial. If I put it outside section scripts, it does not work on full view since I have my scripts on the bottom, and it's trying to run the script before the actual JavaScript files are being loaded.
I was wondering of a good way to implement this. I can move my scripts at top of the page and that should solve all problems. I could also have 2 different scripts but that might mean I have to write the same code twice.
Does anyone know a nice way to implement this? Maybe a Html helper that loads the script in #section scripts {} or in regular script tags depending on the Request type?
Basically, what is the proper way to write this:
#if (!Request.IsAjaxRequest())
{
<text>
#section scripts
{
<script>
$(function () {
$("#tabs").tabs();
});
</script>
}
</text>
}
else
{
<text>
<script>
$(function () {
$("#tabs").tabs();
ga('set', 'page', '/new-page.html'); // Update Google Analytics
});
</script>
</text>
}
Thanks in advance for the help.
you can simply check that in view()
#if (Request.IsAjaxRequest())
{
<script>
alert('Partial view');
</script>
}
else
{
<script>
alert('Main view');
</script>
}
An idea, which I haven't had the chance to test: Store some information on whether or not the request is an ajax request in the view (e.g. in a data attribute), and then, in your script test that value and do accordingly.
E.g.:
<body data-isajaxrequest="#Request.IsAjaxRequest()">...</body>
And then the script:
$(function () {
function InitializeTabs() {
$("#tabs").tabs();
if ($("body").data("isajaxrequest")) {
// it's an ajax request
} else {
// not an ajax request
ga('set', 'page', '/new-page.html'); // Update Google Analytics
}
}
$('#tabsContainer').InitializeTabs();
});
Place your logic as needed where the comments are. The actual script would go into the full view. As another poster mentioned, you shouldn't have scripts inside partial views.
You can try native JavaScipt way to this solution:
var myFunction = function () {
alert('myFunction called after 500ms.');
// do a lot of stuff here
}
// and then call the function after the page is loaded
// let's supose an half second later:
setTimeout(myFunction, 500);

localization(i18n) in sapui5 for fragment.xml file in not appearing

I have a button (create Application) if i click on a button a fragmented dialog will be appearing . here am able to show fragmented dialog .but internalization(i18n) is not appearing for the fields. (For xml files able to show i18n but for fragment.xml file not able to show i18n/)
component.js:
createContent : function() {
// create root view
var oView = sap.ui.view({
id : "app",
viewName : "sap.gss.program.view.App",
type : "JS",
viewData : { component : this }
});
var i18nModel = new sap.ui.model.resource.ResourceModel({
bundleUrl : "i18n/appTexts_fr.properties"
});
oView.setModel(i18nModel, "i18n");
return oView;
}
Controller.js:
createApplication: function (oEvent) {
if (!this.oDialogFragment) {
this.oDialogFragment = sap.ui.xmlfragment("sap.gss.program.view.myFragment",
this);
}
this.oDialogFragment.open();
}
fragment.xml:
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core"
xmlns:app="http://schemas.sap.com/sapui5/extension/sap.ui.core.CustomData/1">
<Dialog
title="{i18n>Title}"
class="sapUiPopupWithPadding" >
<HBox>
<Text text="{i18n>Description_TEXT}" > </Text>
</HBox>
<beginButton>
<Button text="{i18n>Ok}" press="DialogButton" />
</beginButton>
<endButton>
<Button text="{i18n>Cancel}" press="CloseButton" />
</endButton>
</Dialog>
</core:FragmentDefinition>
You can use the dependents aggregation, to connect up the dialog to the view; you don't need to set any models explicitly.
So in your case you would do this:
createApplication: function (oEvent) {
if (!this.oDialogFragment) {
this.oDialogFragment = sap.ui.xmlfragment("sap.gss.program.view.myFragment", this);
}
this.getView().addDependent(oDialogFragment); // <--
this.oDialogFragment.open();
}
See my answer to 'What is the usage of "dependents" aggregation in SAPUI5?' for more details.
you should set the i18n resource model for the dialog fragment as well.
createApplication: function(oEvent) {
if (!this.oDialogFragment) {
this.oDialogFragment = sap.ui.xmlfragment("sap.gss.program.view.myFragment", this);
var i18nModel = new sap.ui.model.resource.ResourceModel({
bundleUrl : "i18n/appTexts_fr.properties"
});
this.oDialogFragment.setModel(i18nModel, "i18n");
}
this.oDialogFragment.open();
}
Its often the easiest way esp. for a ResourceModel to just set it globally:
sap.ui.getCore().setModel(i18nModel, "i18n");
Now you can reference it from everywhere in your application and bind to it like you did, no need to ever set it again on view- or even control-level.
I had same problem, so setup model in Component globally and locally. It is working correctly.
sap.ui.getCore().setModel(i18nModel, "i18n");
this.setModel(i18nModel, "i18n");

injecting $http into directive controller and minification

I'm getting one of these in my prod environment
[$injector:unpr] http://errors.angularjs.org/1.2.2/$injector/unpr?p0=nProvider
My directive is something along these lines
var app = angular.module('myDir', []);
function link(scope, elm, attrs) {
scope.$watch('Potato', function (nVal) {
if (nVal) {
elm.html(Potato);
}
}
function someDir(http, compile) {
function controller($scope, http) {
http.get('/some/service/' + $scope.someThing).success(function (result) {
scope.Potato = result;
console.log('hooray');
});
};
var dirObject = {
restrict: 'A',
scope: {
'someThing': '='
},
link: link,
controller: controller
};
return dirObject;
};
app.directive('someDir', ['$http', '$compile', someDir]);
Is this how I should be handling injecting $http into a directive controller? Solution is hosted on azure, and minified via ASP.NET MVC bundles.
If I render all my script files individually instead of letting them get minified, everything works, but when they get minified I get the unknown provider error.
Adding controller that is running outside of the directive.
angular.element(document).ready(function () {
"use strict";
var profileApp = angular.module('profileApp', ['myDir']);
function ProfileCtrl(scope) {
scope.companyId = angular.element("#Id").val();
};
profileApp.controller('ProfileCtrl', ['$scope', ProfileCtrl]);
angular.bootstrap(document, ['profileApp']);
});
You should return the dirObject from someDir(). I guess this could be a typo.
Also the controller definition is wrong. Use the array injection, if you want the minified version to run:
function controller($scope, http) { ... }
var dirObject = {
...
controller: ["$scope", "$http", controller]
}
return dirObject;

How do I prevent IE save file dialog when using fileupload in asp.net mvc

When I try and upload a file using ASP.NET MVC it works fine in FF && Chrome, but in IE and Opera a dialog pops-up which asks me to either download, save or cancel.
Choosing either of the options, prevents the fileupload from working. How can I get round this problem?
public class ImportModel
{
[Required]
[FileExtensions("csv", ErrorMessage = "Please upload a valid .csv file")]
public HttpPostedFileBase File { get; set; }
}
[HttpPost]
public virtual ActionResult StartImport(ImportModel model = null)
{
if (model != null)
{
var status = _importService.StartImport(model);
return Json(status, JsonRequestBehavior.AllowGet);
}
return null;
}
View code below (summarised):
<div id="dlgImport" class="hidden">
#using (Html.BeginForm(MVC.Import.StartImport(), FormMethod.Post, new { #class = "smallForm", id = "frmImport", enctype = "multipart/form-data" }))
{
<div class="fields-inline">
<div class="editor-label">
#Html.Label("File")
</div>
<div class="editor-field">
#Html.TextBoxFor(x => x.File, new { #class="input-file", type = "file" })
#Html.ValidationMessageFor(x => x.File)
</div>
</div>
}
</div>
</div>
$(function() {
$("#frmImport").ajaxForm({
success: function (responseHtml) {
// response is wrapped in pre tags by the browser - the ajax upload is carried out using an iframe
var response = $.parseJSON($(responseHtml).text());
}
});
});
var vm = {
startImport: function () {
if ($("#frmImport").valid()) {
$("#frmImport").submit();
}
}
}
Now that you have posted your code it looks like that you are using the jquery form plugin. As explained in the documentation this plugin supports file uploads using AJAX but you cannot return JSON from your server side script:
Since it is not possible to upload files using the browser's
XMLHttpRequest object, the Form Plugin uses a hidden iframe element to
help with the task. This is a common technique, but it has inherent
limitations. The iframe element is used as the target of the form's
submit operation which means that the server response is written to
the iframe. This is fine if the response type is HTML or XML, but
doesn't work as well if the response type is script or JSON, both of
which often contain characters that need to be repesented using entity
references when found in HTML markup.
To account for the challenges of script and JSON responses, the Form
Plugin allows these responses to be embedded in a textarea element and
it is recommended that you do so for these response types when used in
conjuction with file uploads. Please note, however, that if there is
no file input in the form then the request uses normal XHR to submit
the form (not an iframe). This puts the burden on your server code to
know when to use a textarea and when not to.
So basically your upload controller action should respond with:
<textarea>{"foo":"bar"}</textarea>
instead of:
{"foo":"bar"}
Also you should not use the application/json response content type in this case.
You could write a custom action result to achieve that:
public class FileJsonResult : JsonResult
{
public FileJsonResult(object data)
: base()
{
Data = data;
JsonRequestBehavior = JsonRequestBehavior.AllowGet;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Write("<textarea>");
base.ExecuteResult(context);
context.HttpContext.Response.Write("</textarea>");
context.HttpContext.Response.ContentType = "text/html";
}
}
and then:
[HttpPost]
public virtual ActionResult StartImport(ImportModel model = null)
{
if (model != null)
{
var status = _importService.StartImport(model);
return new FileJsonResult(status);
}
new FileJsonResult(new { status = false, errorMessage = "The model was null" });
}
Now you might also need to adapt your success handler to strip the <textarea> tags:
$('#frmImport').ajaxForm({
success: function (responseHtml) {
var responseHtml = responseHtml
.replace(/\<textarea\>/i, '')
.replace(/\<\/textarea\>/i, '');
var response = $.parseJSON(responseHtml);
// do something with the response
}
});
I had the same problem with IE8 and this answer helped me a lot. But I needed to make some changes that worked very well in IE8, Chrome and Firefox.
Follow changes below:
success: function (responseText) {
try{
var response = $.parseJSON(responseText);
//code ...
}
catch(ex) {
//error code
}
}
[HttpPost]
public JsonResult Upload(HttpPostedFileBase file) {
//code
return Json(new { Success = "true", Message = "Done!" }, "text/html");
}

Resources