calling controller action from Vuejs, from different views - c#

I have (topbar + sidebar + bottombar) as Vuejs components that are part of page layout within every View is rendered.
In top bar vuejs file i have a method that is loading "some" data via API request to one of my controller:
beforeMount() {
this.loggedUserDeputies.userDeputies = [];
console.log("calling LoadLoggedUserDeputies");
$.get('api/Timesheets/LoadLoggedUserDeputies')
.then(response => response as Promise<LoggedUserDeputies>)
.then(data => {
this.loggedUserDeputies = data;
});
},
API controller method:
[Route("api/[controller]/[action]")]
public object LoadLoggedUserDeputies()
{
if (ActualUserContract.LoggedUserDeputies == null)
{
return null;
}
var result = ActualUserContract.LoggedUserDeputies
.ToList()
.Select(x => new
{
userContractId = x.UserContract.Id,
userContractFullName = x.UserFullName,
userContractPersonalNumber = x.UserContract.PersonalNumber
});
return new { userDeputies = result };
}
but i have an issue, that the url for fetch the data is always modified based on current view i am in.
(so instead of always call: https://localhost:44380/api/Timesheets/LoadLoggedUserDeputies
it is transformed to: https://localhost:44380/Activities/api/Timesheets/LoadLoggedUserDeputiesor https://localhost:44380/Reports/api/Timesheets/LoadLoggedUserDeputies
). Even when its called by the same layout component (which was new to me, so apologies for my ignorance).
Is there any way i can always call the same url: https://example.com/api/Timesheets/LoadLoggedUserDeputies from every view ?

You are using a relative path to load the file:
$.get('api/Timesheets/LoadLoggedUserDeputies')
You need a slash on the front of it to make it an absolute path
$.get('/api/Timesheets/LoadLoggedUserDeputies')
that will load from the current server. If you want to specify the server, too:
$.get('https://example.com/api/Timesheets/LoadLoggedUserDeputies')

Related

Uncaught (in promise) TypeError: data.map is not a function (React using axios getting data from ASP.NET API)

I've created an API using ASP.NET, and I've got a Website running React. I'm wanting to display the data retrieve with a get request from the API to React using Axios. The website has an authentication method using two cookies. I can get the Axios to get data from https://jsonplaceholder.typicode.com/users, but when i use the same bit of code, then I get the error: Uncaught (in promise) TypeError: data.map is not a function.
I've tried as mentioned above to use a placeholder, and that works fine, but can't seem to get thedata from my API, which leads me to believe that the problem lies in the cookies. I've also tried a few Google searches, which returned that I should include withCredentials: true, but that doesn't do the trick.
Here is the function from my API:
public JsonResult YearlyManagersJSON(int year = 0)
{
if (year < 2000 || year > DateTime.Today.Year)
year = DateTime.Today.Year;
var startDate = new DateTime(year, 1, 1);
var endDate = new DateTime(year + 1, 1, 1);
var bonds = this.getOverviewData(ReportType.BONDS, startDate, endDate);
var bondsSum = bonds.Sum(m => m.Aggregate);
var viewData = new TopLeadManagerViewData
{
Title = String.Format("Top Managers in {0}", startDate.Year),
Currency = SiteHelper.getCurrencyToUse(),
Bonds = Enumerable.Select(bonds, m => new ManagerSummary()
{
NumberOfIssues = (int)m.Aggregate2,
TotalAmount = m.Aggregate * 1000000,
Name = m.Group.ToString(),
Share = 100.0m * m.Aggregate / bondsSum
}),
};
return this.Json(viewData, JsonRequestBehavior.AllowGet);
}
This returns a JSON, which i have checked using Postman. Then i try to access the data using axios.
state = {
yearlyBonds: []
}
componentDidMount() {
axios.get(
'http://localhost/Stamdata.Web/LeagueTable/YearlyManagersJSON',
{ withCredentials: true }
)
.then(res => {
const yearlyBonds = res.data;
this.setState({ yearlyBonds });
})
}
render() {
return (
// Tags removed for simplicity
<ListTable data={this.state.yearlyBonds.Bonds} />
The data is then passed down into the component
function ListTable(props) {
const { classes, header, data } = props;
return(
// Tags removed for simplicity
<TableBody>
{data.map((x, i) => {
return(
<TableRow key={i}>
<TableCell scope="row">{x.Name}</TableCell>
<TableCell scope="row">{x.TotalAmount}</TableCell>
<TableCell scope="row">{x.Share}</TableCell>
<TableCell scope="row">{x.NumberOfIssues}</TableCell>
</TableRow>
)
})}
</TableBody>
So, this returns the error
"Uncaught (in promise) TypeError: data.map is not a function", which I would like to have display the data retrieved.
Your initial state is,
yearlyBonds: []
When component first renders it takes initial state. Initially you have empty array. So iteration over empty array giving you the error.
You can conditionally add your component like,
{ this.state.yearlyBonds.Bonds && <ListTable data={this.state.yearlyBonds.Bonds} />}
Note that, componentDidMount gets called after 1st render (when component mounts into DOM).
You have following workflow for the component -
During the 1st render, you have a state -
state = {
yearlyBonds: []
}
In the render function, you want to pass Bonds key data which doesn't exist in your initial state until API call is made and state has Bonds data.
Since this.state.yearlyBonds.Bonds is undefined during initial render, you can't call map method on undefined object. That is why you're seeing that error.
Now to fix this, there are quite a few methods:
Method #1 (Simplest):
Update your state like this -
state = {
yearlyBonds: {
bonds: []
}
}
Your render would work without any additional changes.
Method #2 (Moderate):
Update your function to accept de-structured props with default value for data
function ListTable({ classes, header, data=[] }) {
// rest of the code goes here
Method #3: (The right approach for API calls):
Add a isLoading flag in your component state. We will use this to show a fallback 'Loading ...' UI until we have data from the API.
state = {
yearlyBonds: [],
isLoading: false,
}
Before API call is made, update your state with 'isLoading' set to true.
componentDidMount() {
// update state
this.setState({ isLoading: true });
axios.get(
'http://localhost/Stamdata.Web/LeagueTable/YearlyManagersJSON',
{ withCredentials: true }
)
.then(res => {
const yearlyBonds = res.data;
// set isLoading to false, as data is received
this.setState({ yearlyBonds, isLoading: false });
})
}
Finally in the render method, read isLoading state and render a fallback.
// rest of the code
render() {
if (this.state.isLoading) {
return <div> Loading ... </div>;
// when data is available
return (
// Tags removed for simplicity
<ListTable data={this.state.yearlyBonds.Bonds} />
It happens because you are iterating over an empty array.

AngularJs - Assign Value to scope variable inline

It's a fairly simple problem, I am using AngularJS v1.7.2 with C# MVC.
I got my standard setup with Layout pages and Views.
I load my AngularJs controllers/services from external script files so there's nothing on the Views.
My problem is that I want to assign a value from ViewBag to a controller variable, but I obviously can't reference ViewBag in a script as it needs to be done on the cshtml page.
I have tried doing it inside ng-init like so
<div ng-init="City = #ViewBag.City"></div>
Or
<div style="visibility: hidden;">{{CityId = '1'}}</div>
I tried variations with {{City = #ViewBag.City}}, '#ViewBag.City' and couple of others I saw on StackOverflow to no avail.
I load my scripts on the view using:
#section Page_Scripts {
#Scripts.Render("~/angular/ngListing")
}
That obviously is loaded in Layout. My controller works fine so that's not the issue.
My controller is making an ajax call upon initialization, at that point I need the $scope.City to be populated with the right value, however it's always set at 0.
Here's what my controller + service (combined for sake of SO) looks like:
_mainApp.controller("ListingCtrl", function ($scope, $http) {
$scope.City = 0;
$scope.Attractions = [];
$scope.Offset = 0;
$scope.Pages = new Array(10);
var MakeRequest = function (offset) {
$http.post("/City/GetStuff?City=" + $scope.City + "&Offset=" + offset).then(function (resp) {
$scope.Attractions = resp.data;
});
}
MakeRequest($scope.Offset);
$scope.PageUp = function () {
$scope.Offset++;
MakeRequest($scope.Offset);
}
$scope.PageDown = function () {
$scope.Offset--;
MakeRequest($scope.Offset);
}
$scope.GoTo = function (offset) {
$scope.Offset = offset;
MakeRequest(offset);
}
});
Any solution that is not hacky-ish would be appreciated. It can include directives or a way to assign a value to $scope.City but inline, or at least get my ViewBag.City value passed to the controller somehow.
Use a function in the ng-init directive:
<div ng-init="initCity(#ViewBag.City)"></div>
Define the function in the controller:
$scope.initCity = function(city) {
$scope.city = city;
MakeRequest($scope.offset, $scope.city);
};
function MakeRequest(offset,city) {
var url = "/City/GetStuff";
var params = {city:city, offset:offset};
var config = {params: params};
$http.get(url,config).then(function (resp) {
$scope.Attractions = resp.data;
});
}
This way the controller will wait for the ng-init directive.

Populate variable from a JSON Result in ASP.NET MVC

I don't know if this is a basic stuff but I'm a having a hard time on populating a variable from a JSON result. I can't just find a right keyword on it.
What I want to do is populate this variable.
Js File
var opts = [
{ key: 1, label: 'High' },
{ key: 2, label: 'Normal' },
{ key: 3, label: 'Low' }
];
Layer
public IEnumerable<DropdownModel> ToLayerLevel()
{
List<DropdownModel> data = new List<DropdownModel>();
using (SqlConnection con = new SqlConnection(Conn.MyConn()))
{
SqlCommand com = new SqlCommand("SELECT id, desc FROM PriorityLevel", con);
con.Open();
SqlDataReader reader = com.ExecuteReader();
while (reader.Read())
{
DropdownModel value = new DropdownModel();
value.key = reader.GetInt32(0);
value.label = reader.GetString(1);
data.Add(value);
}
}
return data;
}
Controller
public JsonResult ddlToLayerLevel()
{
DropdownLayers ddl = new DropdownLayers();
var data = ddl.ToLayerLevel();
return Json(data, JsonRequestBehavior.AllowGet);
}
You can make an ajax call to the server action method where it will return the data you need
public JsonResult ddlToLayerLevel()
{
var ddl = new DropdownLayers();
var data = ddl.ToLayerLevel().ToList();
return Json(data, JsonRequestBehavior.AllowGet);
}
This will return an array of items with key and label properties, in JSON format
[{"key":1,"label":"High"},{"key":2,"label":"Normal"},{"key":3,"label":"Low"}]
Now you can call this action method using ajax . Here is a sample using jQuery $.get method.
var url="/YourControllerName/ddlToLayerLevel";
$.get(url).done(function(resultArray) {
// resultArray is an array of items.
// Use it inside this callback method scope
var opts=resultArray;
console.log(opts);
});
To avoid 404 Not found errors, when making ajax call's from external js files, you may use the Url.Action method to generate the correct relative path in your view file(where you can execute C# methods like Url.Action) and use that in your external js file.
For example, you can do like this in your razor view file
<script>
var myProject= myProject|| {};
myProject.layerLevelUrl ="#Url.Action("ddlToLayerLevel","YourControllerName")";
</script>
<script src="pathToYourExternalJsFile.js"></script>
Now in your external js file you can use it like
$.get(myProject.layerLevelUrl)
.done(function(resultArray) {
// resultArray is an array of items. Use it inside this callback method
var opts=resultArray;
console.log(JSON.stringify(opts));
});
Getting data using jQuery:
var opts = [];
$.getJSON("<your url here>", function(res){
opts = res;
});
There is an alternate simplified way I use when I need to get objects created in c# directly into a js variable. It does come with pros and cons though
pros
Much quicker to code rather than having to create a controller and call a js ajax function every time.
Loads the entire page in one go and overall loads the data into js faster.
cons
The code doesn't go into your js file but instead into a cshtml file.
The information is only fetched in load time and the code can't be reused in real time when the page is still open.
If you don't need the js variable on page load, not using this would speed up the initial page load speed and will load the js information later.
First I create an extension function in static class as follows:
public static IHtmlContent ToJS(this IHtmlHelper htmlHelper, object obj)
{
return htmlHelper.Raw(JsonConvert.SerializeObject(obj));
}
Now in any cshtml file I can define the js variable by simply using the following:
#{
DropdownLayers ddl = new DropdownLayers();
var data = ddl.ToLayerLevel();
}
<script>
var opts = #Html.ToJS(data);
</script>

Angular Web api file Download [duplicate]

HTML:
<a href="mysite.com/uploads/asd4a4d5a.pdf" download="foo.pdf">
Uploads get a unique file name while there real name is kept in database. I want to realize a simple file download. But the code above redirects to / because of:
$routeProvider.otherwise({
redirectTo: '/',
controller: MainController
});
I tried with
$scope.download = function(resource){
window.open(resource);
}
but this just opens the file in a new window.
Any ideas how to enable a real download for any file type?
https://docs.angularjs.org/guide/$location#html-link-rewriting
In cases like the following, links are not rewritten; instead, the
browser will perform a full page reload to the original link.
Links that contain target element Example:
link
Absolute links that go to a different domain Example:
link
Links starting with '/' that lead to a different base path when base is defined Example:
link
So in your case, you should add a target attribute like so...
<a target="_self" href="example.com/uploads/asd4a4d5a.pdf" download="foo.pdf">
We also had to develop a solution which would even work with APIs requiring authentication (see this article)
Using AngularJS in a nutshell here is how we did it:
Step 1: Create a dedicated directive
// jQuery needed, uses Bootstrap classes, adjust the path of templateUrl
app.directive('pdfDownload', function() {
return {
restrict: 'E',
templateUrl: '/path/to/pdfDownload.tpl.html',
scope: true,
link: function(scope, element, attr) {
var anchor = element.children()[0];
// When the download starts, disable the link
scope.$on('download-start', function() {
$(anchor).attr('disabled', 'disabled');
});
// When the download finishes, attach the data to the link. Enable the link and change its appearance.
scope.$on('downloaded', function(event, data) {
$(anchor).attr({
href: 'data:application/pdf;base64,' + data,
download: attr.filename
})
.removeAttr('disabled')
.text('Save')
.removeClass('btn-primary')
.addClass('btn-success');
// Also overwrite the download pdf function to do nothing.
scope.downloadPdf = function() {
};
});
},
controller: ['$scope', '$attrs', '$http', function($scope, $attrs, $http) {
$scope.downloadPdf = function() {
$scope.$emit('download-start');
$http.get($attrs.url).then(function(response) {
$scope.$emit('downloaded', response.data);
});
};
}]
});
Step 2: Create a template
Download
Step 3: Use it
<pdf-download url="/some/path/to/a.pdf" filename="my-awesome-pdf"></pdf-download>
This will render a blue button. When clicked, a PDF will be downloaded (Caution: the backend has to deliver the PDF in Base64 encoding!) and put into the href. The button turns green and switches the text to Save. The user can click again and will be presented with a standard download file dialog for the file my-awesome.pdf.
Our example uses PDF files, but apparently you could provide any binary format given it's properly encoded.
If you need a directive more advanced, I recomend the solution that I implemnted, correctly tested on Internet Explorer 11, Chrome and FireFox.
I hope it, will be helpfull.
HTML :
<i class="fa fa-file-excel-o"></i>
DIRECTIVE :
directive('fileDownload',function(){
return{
restrict:'A',
scope:{
fileDownload:'=',
fileName:'=',
},
link:function(scope,elem,atrs){
scope.$watch('fileDownload',function(newValue, oldValue){
if(newValue!=undefined && newValue!=null){
console.debug('Downloading a new file');
var isFirefox = typeof InstallTrigger !== 'undefined';
var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
var isIE = /*#cc_on!#*/false || !!document.documentMode;
var isEdge = !isIE && !!window.StyleMedia;
var isChrome = !!window.chrome && !!window.chrome.webstore;
var isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
var isBlink = (isChrome || isOpera) && !!window.CSS;
if(isFirefox || isIE || isChrome){
if(isChrome){
console.log('Manage Google Chrome download');
var url = window.URL || window.webkitURL;
var fileURL = url.createObjectURL(scope.fileDownload);
var downloadLink = angular.element('<a></a>');//create a new <a> tag element
downloadLink.attr('href',fileURL);
downloadLink.attr('download',scope.fileName);
downloadLink.attr('target','_self');
downloadLink[0].click();//call click function
url.revokeObjectURL(fileURL);//revoke the object from URL
}
if(isIE){
console.log('Manage IE download>10');
window.navigator.msSaveOrOpenBlob(scope.fileDownload,scope.fileName);
}
if(isFirefox){
console.log('Manage Mozilla Firefox download');
var url = window.URL || window.webkitURL;
var fileURL = url.createObjectURL(scope.fileDownload);
var a=elem[0];//recover the <a> tag from directive
a.href=fileURL;
a.download=scope.fileName;
a.target='_self';
a.click();//we call click function
}
}else{
alert('SORRY YOUR BROWSER IS NOT COMPATIBLE');
}
}
});
}
}
})
IN CONTROLLER:
$scope.myBlobObject=undefined;
$scope.getFile=function(){
console.log('download started, you can show a wating animation');
serviceAsPromise.getStream({param1:'data1',param1:'data2', ...})
.then(function(data){//is important that the data was returned as Aray Buffer
console.log('Stream download complete, stop animation!');
$scope.myBlobObject=new Blob([data],{ type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
},function(fail){
console.log('Download Error, stop animation and show error message');
$scope.myBlobObject=[];
});
};
IN SERVICE:
function getStream(params){
console.log("RUNNING");
var deferred = $q.defer();
$http({
url:'../downloadURL/',
method:"PUT",//you can use also GET or POST
data:params,
headers:{'Content-type': 'application/json'},
responseType : 'arraybuffer',//THIS IS IMPORTANT
})
.success(function (data) {
console.debug("SUCCESS");
deferred.resolve(data);
}).error(function (data) {
console.error("ERROR");
deferred.reject(data);
});
return deferred.promise;
};
BACKEND(on SPRING):
#RequestMapping(value = "/downloadURL/", method = RequestMethod.PUT)
public void downloadExcel(HttpServletResponse response,
#RequestBody Map<String,String> spParams
) throws IOException {
OutputStream outStream=null;
outStream = response.getOutputStream();//is important manage the exceptions here
ObjectThatWritesOnOutputStream myWriter= new ObjectThatWritesOnOutputStream();// note that this object doesn exist on JAVA,
ObjectThatWritesOnOutputStream.write(outStream);//you can configure more things here
outStream.flush();
return;
}
in template
<md-button class="md-fab md-mini md-warn md-ink-ripple" ng-click="export()" aria-label="Export">
<md-icon class="material-icons" alt="Export" title="Export" aria-label="Export">
system_update_alt
</md-icon></md-button>
in controller
$scope.export = function(){ $window.location.href = $scope.export; };

ASP.Net C# POST not returning view

Using ASP.NET, C# and Javascript, I'm trying to dynamically get Data for the user, POST it to a controller, and return a view that changes depending on the Data.
Here's the code :
Javascript function :
function editEntry(id) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", "Edit?id=" + id, true);
xmlhttp.send({ id: id });
//xmlhttp.send();
}
Controller handling post (a portion) :
[HttpPost]
public ActionResult Edit(EditEvenementiel edit)
{
var contexte = new intranetEntities1();
SqlParameter Id_viewBag = new SqlParameter("#id", edit.id);
ViewBag.edit = contexte.evenementiel
.SqlQuery("SELECT * FROM evenementiel WHERE id_evenementiel = #id", Id_viewBag);
return View();
}
when i fire the javascript, i can see the POST in the firebug console (working fine), i can see the variable getting the correct value in Visual Studio's Debugger, but the view doesn't change.
I even see the expected view (with all the treatements expected) returned in the firebug console; but my page still doesn't change.
How can i do that ?
By default, you should have 2 Actions, one that should process/get the data through a Post method and one that collects data for the View. (it's called Post/Redirect/Get - more details on wiki)
Having this in mind, you can leave your post method as :
[HttpPost]
public ActionResult Edit(int id)
{
var contexte = new intranetEntities1();
SqlParameter Id_viewBag = new SqlParameter("#id", id);
EditEvenementiel edit = contexte.evenementiel.SqlQuery("SELECT * FROM evenementiel WHERE id_evenementiel = #id", Id_viewBag);
return RedirectToAction("Edit",new { edit = edit} );
}
and create a new action which sends the data to the view.
Something like:
public ActionResult Edit(EditEvenementiel edit)
{
//logic here
return View(edit);
}
Please be aware that this is just an example, modify it according to your scenario.
As you are using Ajax (XMLHttpRequest) to fetch this data you also need to present it on your page, it wont happen automatically.
Maybe something like this?
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
alert(xmlhttp.responseText); // or put the responseText in a HTML element of your choice to do whatever you want to do
}
}
Where do you actually update anything on the page? All you do is send the request:
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", "Edit?id=" + id, true);
xmlhttp.send({ id: id });
But you ignore the response. The browser isn't going to know what you want to do with that response, you have to tell it. Which could be something as simple as:
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
document.getElementById('someElement').innerHTML = xmlhttp.responseText;
}
}
Basically, use the AJAX response (which is an HTML view?) to update the page content.

Categories

Resources