MVC refuses a connection to only one action - angularjs

Below is part of the Angular controller I use to populate three cascading lists of address data, with the last being a BootStrap Tab Page widget. I have only tested on Chrome and Edge so far, with similar results.
public class AreaController : BaseController
{
private readonly AreaClient _areaClient = new AreaClient(UserHelper.CurrentUser);
private readonly AgentClient _agentClient = new AgentClient();
[HttpPost]
public JsonResult ProvincesJson()
{
return Json(_areaClient.GetProvinces().ToList());
}
[HttpPost]
public JsonResult AreasJson(int provinceId)
{
var model = _areaClient.GetAreas(provinceId).ToList();
return Json(model);
}
[HttpGet]
public JsonResult SuburbsJson(int agentId, int areaId)
{
var allBurbs = _areaClient.GetSuburbs(areaId).ToList();
var agentBurbIds = _agentClient.GetAgentSuburbs(agentId).Select(ab => ab.SuburbId).ToList();
var model = allBurbs.Select(burb => new CheckListItemModel { Id = burb.SuburbId, Label = burb.SuburbName, IsChecked = agentBurbIds.Contains(burb.SuburbId) }).ToList();
return Json(model);
}
}
ProvincesJson and AreasJson work perfectly for this partial view:
<div id="areas-and-suburbs" ng-controller="areasCtrl">
<div class="form-group">
<select id="ProvinceId" ng-model="geo.provinces.selectedId" ng-change="geo.getAreas(agentId, geo.provinces.selectedId)" class="form-control">
<option ng-repeat="item in geo.provinces" ng-value="{{item.provinceId}}" class=".area-option">{{item.provinceName}}</option>
</select>
</div>
<div class="form-group">
<select id="AreaId" ng-model="geo.areas.selectedId" ng-change="geo.setSuburbs(geo.areas.selectedId)" class="form-control">
<option ng-repeat="item in geo.areas" ng-value="{{item.areaId}}" class=".area-option">{{item.areaName}}</option>
</select>
</div>
<div class="form-group">
<div id="area-suburbs-partial">
#Html.Partial("_Suburbs")
</div>
</div>
</div>
The inner partial, _Suburbs looks like this:
$scope.geo.getSuburbs = function(agentId, areaId) {
var geoUrl = "\/Area/SuburbsJson";
$http.post(geoUrl, { areaId: 3, agentId: 1 }, postOptions)
.then(function(response) {
var model = angular.fromJson(response.data);
$scope.agentSuburbs = model.$values;
_.defer(function() {
$scope.$apply();
});
},
function error(response) {
alert("Ajax error [getSuburbs]: " + response.responseText);
});
};
Yet when the outer parial renders __Suburbs, which calls geo.setSuburbs, I get "localhost refused to connect" error in Chrome. Everything in this project is same domain, just one project, and the Provinces and Areas dropdowns cascade properly, but when I select a new Area, to trigger fetching suburbs for that area, I get the error.
I see very little difference between the three actions, so I really don't understand why a connection to the third is refused. I even removed the business logic from SuburbsJson to return a simple array of int, and called it directly from the browser, vs. from my Angular controller's Ajax logic, and I still got a refused connection.
What could be behind just this one controller action causing a refused connection?
BREAKING:
I was a touch dyslexic somewhere with the spelling of area. Fixing that solved everything on that day.

For one, you're POSTing to your GET method, so MVC won't route it for you.
HTTPGET Methods in MVC have to have the JsonRequestBehavior.AllowGet flag as the second parameter of the return Json.
Instead
// Do other stuff to get model
return Json(model, JsonRequestBehavior.AllowGet);
See this other SO post for why AllowGet is necessary

Related

Display data from a SQL Server stored procedure in an ASP.NET MVC view

The stored procedure that I have carried out in SQL Server returns the following information:
I show what I have worked on so far.
Model
public class TarjetasInformativas
{
public string PrimerNombre { get; set; }
public decimal PrimerMonto { get; set; }
}
Class in which I make my connection to the database
public class DatosTarjetasInformativas
{
public List<TarjetasInformativas> RetornarNombres()
{
List<TarjetasInformativas> objTarjetas = new List<TarjetasInformativas>();
using(SqlConnection sqlConnection = new SqlConnection("Data Source=HN123; Initial Catalog=DBTEST; Integrated Security=True"))
{
string query = "SP_TARJETASINFORMATIVAS";
SqlCommand cmd = new SqlCommand(query, sqlConnection);
cmd.CommandType = CommandType.StoredProcedure;
sqlConnection.Open();
using (SqlDataReader dr = cmd.ExecuteReader())
{
while (dr.Read())
{
objTarjetas.Add(new TarjetasInformativas()
{
PrimerNombre = dr["ENTIDADES"].ToString(),
PrimerMonto = decimal.Parse(dr["MONTO"].ToString()),
});
}
}
}
return objTarjetas;
}
}
Controller
public ActionResult ObtenerNombres()
{
DatosTarjetasInformativas objDTTarjetas = new DatosTarjetasInformativas();
List<TarjetasInformativas> objTarjetas = objDTTarjetas.RetornarNombres();
return View(objTarjetas);
}
View
#model WebPlantillaOpexLTE.Models.TarjetasInformativas
<div class="col-lg-3 col-6">
<!-- small box -->
<div class="small-box bg-info">
<div class="inner">
<h3>#Html.LabelFor(m => m.PrimerNombre)</h3>
<p>#Html.LabelFor(m => m.PrimerMonto)</p>
</div>
<div class="icon">
<i class="ion ion-android-locate"></i>
</div>
</div>
</div>
Within my h3 and p tags of my view, I'm looking to get the information from my SQL Server stored procedure.
Through the Html.LabelFor helper I was able to get only the variables that I declared in my model, and I need to display the content of my SQL Server stored procedure inside the card.
In case of doubt, within my model where I relate to my stored procedure through a breakpoint, I have verified that it receives the information.
I am new to this platform, and I would like to know what I could do to solve my problem.
I thank you in advance for taking the time to pay attention to my question and for the help.
So, there is a couple things wrong in your Razor view.
#model should be of type List<WebPlantillaOpexLTE.Models.TarjetasInformativas> since this is the returning type of your SP function and the model returned to the view.
To show the contents of a list, you should use an foreach loop to iterate over your model, something like the following (may contain syntax error):
#model List<WebPlantillaOpexLTE.Models.TarjetasInformativas>
<div class="col-lg-3 col-6">
#foreach(var item in #Model){
<!-- small box -->
<div class="small-box bg-info">
<div class="inner">
<h3>#item.PrimerNombre</h3>
<p>#item.PrimerMonto</p>
</div>
<div class="icon">
<i class="ion ion-android-locate"></i>
</div>
</div>
}
</div>
Some reference: https://learn.microsoft.com/en-us/aspnet/core/mvc/views/overview?view=aspnetcore-7.0
I achieved what was required, but I made some changes to my controller and deleted my class where I made the connection to my database. I did it with Entity Framework.
I show my controller, in my model and view nothing changes except what was corrected in the first answer.
public ActionResult ListarTarjeta()
{
List<TarjetasInformativas> listaTarjetas = new List<TarjetasInformativas>();
using (db)
{
var listTarjetasInformativas = db.SP_TARJETASINFORMATIVAS().ToList();
foreach(var item in listTarjetasInformativas)
{
var asignar = new TarjetasInformativas
{
PrimerNombre = item.NOMBRE,
PrimerMonto = (decimal)item.MONTO
};
listaTarjetas.Add(asignar);
}
}
return View(listaTarjetas);
}

AngularJs Auto Complete Search

So this works with static data, but when I push data with a $http this autocomplete does not work. The data pushes to the empty array of airport_list but something is happening when I try to use airport_list in for the autocomplete. Not sure what is is. I can only find answers which pertain to static data.
This is updated per everyones help.
Here is the controller
app.controller('selectCtrl', function($scope, $http) {
$scope.airport_list = null;
$http({
url: 'someUrl.com',
method: 'GET'
})
.then((response) => {
angular.forEach(response.data.airports, function(value, key) {
$scope.airport_list = response.data.airports;
})
$scope.airports = $scope.airport_list;
});
$scope.selectAirport = function(string) {
$scope.airport = string;
$scope.hidelist = true;
};
})
Here is the template
<div class="control">
<div>
<input
type="text"
name="airport"
id="airport"
ng-model="airport"
ng-change="searchFor(airport)"
placeholder="From..."
/>
<div class="airport-container-dropdown" ng-hide="hidelist">
<div
class="airport-list"
ng-repeat="airport in airports"
ng-click="selectAirport(airport)"
>
{{ airport.name }}
</div>
</div>
</div>
</div>
I really would like to do this without using bootstrap typeahead.
Thank you for looking at this.
I have made changes as recommended by below answers and the $http request is feeding into the autocomplete as a whole list but searching by name does not work and clicking on name sets [object, object]
this would be the code which is specific to that functionality.
$scope.searchFor = function(string) {
$scope.hidelist = false;
const output = [];
angular.forEach($scope.airport_list, function(airport) {
if (airport[0].toLowerCase().indexOf(string.toLowerCase(airport)) >=
0) {
output.push(airport);
}
});
$scope.airports = output;
};
$scope.selectAirport = function(string) {
$scope.airport = string;
$scope.hidelist = true;
};
Try this:
$scope.airport_list = response.data.airports;
What I am seeing is that you have an array: $scope.airport_list = [];
When you make your http request, you push what I would understand to be an array of airports into that array. So you end up with your airport array from the backend at the first position of $scope.airport_list, vs. $scope.airport_list being the actual list.
For your search method, you should change the following:
In your HTML:
ng-change="searchFor(airport.name)"
In your JS:
I've renamed your function and changed the input variable to be more clear. You were passing in a full airport, but treating it as a string. You need to compare your provided airport name to that of the airports in the array. So you iterate over the array, and compare each element's name property to what you pass in.
$scope.searchFor = function(airportName) {
$scope.hidelist = false;
const output = [];
angular.forEach($scope.airport_list, function(airport) {
if (airport.name.toLowerCase() === airportName) {
output.push(airport);
}
});
$scope.airports = output;
console.log($scope.airports);
};
I have provided minimal changes to your code to implement this, however I suggest you look at this SO post to filter drop down data more appropriately.
Angularjs Filter data with dropdown
If you want to simply filter out what is displayed in the UI, you can try this in your HTML template. It will provide a text field where you supply a partial of the airport name. If at least one character is entered in that box, the list will display on the page, with the appropriate filtering applied. This will avoid having to call functions on change, having a separate array, etc.
<input type="text" name="airport" id="airport" ng-model="airportSearch.name" placeholder="From..." />
<div class="airport-container-dropdown" ng-hide="!airportSearch.name">
<div class="airport-list"
ng-repeat="airport in airport_list | filter:airportSearch"
ng-click="selectAirport(airport)">
{{ airport.name }}
</div>
</div>

Filter card list with Razor View

Im trying to make a filter like in AngularJS when u use:
ng-repeat="u in users | filter:searchBar">
And your input filter looks like
<input type="text" id="searchBar" placeholder="start typing" ng-model="searchBar">
But the things its that im working on MVC with Razor View and I do not know how to approach this filter.
The list of cards is made with a foreachlike this:
#foreach{ var item in Models){
<div class="card">
<div class="card-container">
some content
</div>
</div>
}
Any ideas?
You can do the filtering with ajax. Here is a server side filtering solution.
First, you should move the code which renders the result to a partial view. Let's say you created a partial view called CustomerList.cshtml. Move the list code to that.
#model IEnumerable<Customer>
#foreach (var item in Model)
{
<div class="card">
<div class="card-container">
#item.Name
</div>
</div>
}
Now in your main view, you can call this partial view and pass the data to it. Wrap the call to the partial view in a container div. Add a input element for user to enter the search key. Assuming your main view is also strongly typed to IEnumerable<Customer>
#model IEnumerable<Customer>
<input type="text" id="search" data-url="#Url.Action("Index")" />
<div id="div-items">
#Html.Partial("CustomerList",Model)
</div>
Now we need to have some javascript code which listen to the keyup event on the search input, read the value of it and make an ajax call to the server where it uses the search key and get the filtered set of data, pass that to the same partial view and return the partial view result.
You can use jQuery $.get method
$(document).ready(function () {
$("#search").keyup(function() {
var v = $(this).val();
$.get($(this).data("url"), { searchKey: v }).done(function(res) {
$("#div-items").html(res);
});
});
});
Now make sure your server action method returns the filtered data like this
public ActionResult Index(string serchKey="")
{
var items = db.Customers.AsQueryable();
if (!String.IsNullOrEmpty(searchKey))
{
items = items.Where(a => a.Name.StartsWith(searchKey));
}
var t = items.ToList();
if (Request.IsAjaxRequest())
{
return PartialView("CustomerList",t );
}
return View(t);
}
Another option is to do client side filtering. on the items. But if i am going that direction, i would choose a client side MVC framework like angular to do that for me

Why does my <a> taghelper route go dead after its route is used by a form post?

This is a ASP.Net Core MVC project.
In my layout file I have the following Menu link:
<li><a asp-controller="Employees" asp-action="Index">Employees</a></li>
The MVC controller it routes to looks like this:
public IActionResult Index()
{
return View();
}
When I click the link the Action is hit and the view is rendered with my Employee List View.
The Employee List View is bound to an Angular Controller which calls a corresponding Employees Web API GET and the view shows my employee list unfiltered.
Great.
Now we need an Employee quick search from a quick search panel.
So I modify my MVC Employees controller like this:
public IActionResult Index(EmployeeListPageViewModel empListPageVM)
{
return View(empListPageVM);
}
It takes in this Model:
public class EmployeeListPageViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
My Quick Search Form looks like this back in the layout file:
<form asp-controller="Employees" asp-action="Index">
<th>Employee Search:</th>
<td><input id="firstName" name="firstName" type="text" placeholder="FirstName" /></td>
<td><input id="lastName" name="lastName" type="text" placeholder="LastName" /></td>
<td>
<button class="btn btn-xs btn-primary glyphicon glyphicon-search">
</button>
</td>
</form>
Now the model is built from the form and sent to my MVC Employees Index action.
And of course I roll all the needed changes through.
Make my Employees Web API controller take in optional params.
FirstName = null, LastName = null.
The employee list view takes in the ViewModel:
#model EmployeeListPageViewModel
Binds to the Angular Controller:
ng-controller="employeesController
Calls getEmployees:
ng-init="getEmployees('#Model.FirstName', '#Model.LastName')
The Angular controller works out whether everything is null or filtering is needed:
/***** List Employees *****/
$scope.getEmployees = function (pfirstName, pLastName) {
var config = {
params: {
firstName: pfirstName,
lastName: pLastName
}
}
$http.get(employeeUrl, config)
.then(function (response) {
// Test front end exception message;
// throw "test exception";
$scope.data.employees = response.data;
})
.catch(function (error) {
$scope.data.employeeListError = error;
});
}
Hope all of this makes sense. Just laying the foundation here.
Now my problem:
Everything seems to work individually.
But, when I go in fresh and click the Employees Menu Link I get my full list.
And when I fill in FirstName And/or LastName in the quick search it works.
But now the Employees menu link is dead. It doesn't fire. It doesn't hit the Employees Index Controller action.
What is it about the form that is killing the Menu Link?
Update 1: After thinking about this I believe the anchor tag helper is looking at the controller and index and saying, "I am already there." So it is not going to the controller action. How do I force it to go even if it is already there?
Update 2: I tried changing the link to this:
<li>Employees</li>
The link works but it is still killed after the form post.
Apparently, no matter how you direct to the link, taghelper, ng-href, straight link, whatever, if you are already there the link will not go.
I had to replace the anchor link in the menu with this:
<li>
<form asp-controller="Employees" asp-action="Index">
<button type="submit" class="navbarLinks">
Employees
</button>
</form>

Backbone.js create method not sending parameters to sinatra

I'm new to backbone and trying to set it up in Sinatra, but I can't seem to get a simple create working.
I've set up my model/collection as so:
var TEAM_ID = window.location.pathname.split('/')[1]; // From url
$(function () {
var TeamMember = Backbone.Model.extend({
defaults: {
name : ""
}
});
var TeamMembers = Backbone.Collection.extend({
model: TeamMember,
url: "/" + TEAM_ID + "/team-members.json"
});
var teamMembers = new TeamMembers;
var TeamMemberView = Backbone.View.extend({
events: {
"click #new-team-member-form .submit-button" : "handleNewTeamMember"
},
handleNewTeamMember: function(data) {
var inputField = $('input[name=new_team_member_name]');
console.log("Pre create");
// This doesn't get sent to the server!!
var teamMember = teamMembers.create({name: inputField.val());
console.log("Post create");
return false; // Don't submit form
},
render: function() {
console.log("Render team member");
return this;
}
});
// ...
var teamMemberView = new TeamMemberView({el: $('#week-view')});
});
The html looks like:
<table id="week-view">
<!-- ... -->
<form id="new-team-member-form" action="/some-add-url" method="post">
<fieldset class="new-object-fieldset" title="New team member">
<legend>New team member</legend>
<label for="new_team_member_name">Add new</label>
<input type="text" name="new_team_member_name" title="Add member" class="new-object-text-box" />
<button type="submit" name="new_team_member" value="new_team_member" class="submit-button">+</button>
<div id="help-new"></div>
</fieldset> <!-- New team member -->
</form>
<!-- ... -->
and the ruby looks like:
post '/:team_id/team-members.json' do
logger.info("Add team member (json): #{params}")
end
However, the sinatra server only shows params[:team_id], without the name parameter on the teamMembers.create line. Am I doing something stupid in backbone? Not initialising something properly?
I've looked at http://documentcloud.github.com/backbone/#Collection-create,
http://documentcloud.github.com/backbone/docs/todos.html, http://liquidmedia.ca/blog/2011/01/backbone-js-part-1/, http://liquidmedia.ca/blog/2011/01/an-intro-to-backbone-js-part-2-controllers-and-views/ and https://gist.github.com/1655019, but I can't seem to find any answers there. I feel like I've done something stupid, but just can't see it!
It turns out, it was me not knowing how to extract json parameters in sinatra properly. From this site: http://mini.softwareas.com/posting-json-to-a-sinatra-mongodb-service, I found out I had to use request.body.read.to_s instead of the params hash ie,
post '/:team_id/team-members.json' do
request_body = JSON.parse(request.body.read.to_s)
team_member_name = request_body["name"]
# ...
end
I had the same problem. I am on PHP, though. Since Backbone sends POST data not in a query string, but rather in a plain JSON string, the data is not available thru $_POST. To read the Backbone POST data:
// the 'true' param returns an array rather than an object
$post = json_decode(file_get_contents('php://input'), true);
You can also read it directly from $HTTP_RAW_POST_DATA.

Resources