AngularJS controller is not reaching webapi - angularjs

In the interest of crawling before walking with angularjs, I have created a simple application to display results from a sql server table using webapi. unfortunately the webapi is never called, because the routing is wrong, but I am not sure how to resolve. fiddler shows a 404 error.
the cshtml is as follows, defining the app and controller.
<script type="text/javascript">
var app = angular.module('streamApp', []);
app.controller('streamController', function($scope, $http){
$scope.loading = true;
$scope.addMode = false;
//Used to display the data
$http.get('/api/Stream/').success(function (data) {
$scope.streams = data;
$scope.loading = false;
})
.error(function () {
$scope.error = "An Error has occured while loading streams!";
$scope.loading = false;
});
});
</script>
the rendering section in the cshtml file is
<div data-ng-app="streamApp" data-ng-controller="streamController" class="container">
....
</div>
The webapi class is in a folder named WebApi in the MVC project, but since it is never reached, there is no point in displaying its code. it is non-descript anyway.
The route config is as follows:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
I am not sure if I need to specify route instructions in angularjs code or in mvc route config, and exactly what functions or configuration to supply. I have tried moving the webapi class to the Controllers folder with the same results - http 404. Any advice on how to make this example get to the webapi code would be appreciated.
well hot-diggity....I solved the immediate problem by adding the annotation to my webapi class method
[Route("api/stream")]
[System.Web.Http.HttpGet]
public IEnumerable<Models.StreamViewModel> Get()
{
.....
}
so now the question is should something be done with RouteConfig as a better practice? Or is the Route() annotation the preferred way to go? or is it 6 of one half a dozen of the other?

Answer to your updated question , Best Practice of routing:
Route() annotation is the preferred way of doing this.
MVC 5 supports a new type of routing, called attribute routing. As the
name implies, attribute routing uses attributes to define routes.
Attribute routing gives you more control over the URIs in your web
application.
The earlier style of routing, called convention-based routing, is
still fully supported. In fact, you can combine both techniques in the
same project.
There are other advantages of attribute routing like
It puts the route information adjacent to the controller action that
implements that route. This helps in debugging and troubleshooting,
as well as providing an ability to quickly search for route
information in your solution.
It reduces risk in the process of making changes to routes. In RouteConfig.cs or WebApiConfig.cs (in the case of Web API solutions),
the possibility exists to inadvertently change the wrong route or
otherwise adversely affect other parts of your application.
You may also want to include acceptable HTTP methods, permitted user types and registration priorities, which if included with the
attribute-based routes, put all of that information together in one
place.
This post provided inspiration and reinforcement for me on the above, and goes into more detail:
http://kevinmontrose.com/2011/07/25/why-i-love-attribute-based-routing/

You can use either, but without the annotation your endpoint would have been api/get not api/stream (assuming you didn't rename your method).

First , you should use ApiController rather then Controller, as it take a role of an api action.
Second, If we take a look, it looks like you created a controller named ApiController, and a function called Stream. Otherwise, its a misunderstanding of the designing your web integration using MVC.
App_Start\WebApiConfig.cs:
using System.Web.Http;
class WebApiConfig
{
public static void Register(HttpConfiguration configuration)
{
configuration.Routes.MapHttpRoute("API Default", "api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
}
}
Global.asax.cs:
using System.Web.Http;
...
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
WebApiConfig.Register(GlobalConfiguration.Configuration);
RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
and ApiControllerNameController: //The Controller word closes the file name, and will be reached without writing it:
using System;
..
..
..
namespace MvcApplication1.Controllers
{
public class ValuesController : ApiController
{
// GET api/values/MethodName
public IEnumerable<int> MethodName()
{
return new List<int> { 1, 2, 3 };
}
}
}

Related

AngularJS: How to initialise components after service finished asynchronous request

We're not using AngularJs as a SPA but embedded module to manage some behavior and shared data, so we're not actually utilising something like angular router. How should I initialize components only after a shared data service finished an asynchronous request? AngularJS was used with Typescript
Angular Module
import ... from '...'
import ... from '...'
...
angular.module('app-1', [])
.service('data-service', DataService)
.component('zeroDateButton', new ZeroDateButtonComponent())
.component('zeroPanel', new ZeroPanelComponent())
.component('zeroChart', new ZeroChartComponent())
ASP.NET Page hosting Angular module
BI.aspx
<asp:Content ID="standardContent" ContentPlaceHolderID="MainContent" runat="server">
...
<zero-date-button></zero-date-button>
<zero-date-button></zero-date-button>
<zero-panel name="panel-1"></zero-panel>
<zero-panel name="panel-2"></zero-panel>
<zero-panel name="panel-3"></zero-panel>
<zero-chart></zero-chart>
...
<script src="Scripts/Components/component.app-1.js) "></script> //compiled angular module js file
</asp:Content>
Page URL: https://www.example.com/BI/Zero
DataService.ts
public tryGetData() {
return $http.get(url).then((res: any) => {
this.panels = res.panels;
});
}
ZeroPanelComponent.ts
...
public $onInit(): void {
this.panels = this.dataService.panels;
this._render();
...
Most of the logics for this module relies on the data from the three components, so I want to fetch and store them all together in the data service, from which each component access the data they need from this service, and let the service figure out the logics and tell each of them by broadcasting the events.
Upon the components initialization(in $onInit method), it should display things using data retrieved from data service. The problem is component initialization is not awaiting data service to finish data fetching, so the component can't get the data they need and render nothing.
Trial with $routeProvider
I've seen seen lot's of people advising $routeProvider with appModule.config(), however it was not working. I'm not sure if this solution will work considering the way we use Angular, but I'm still posting the code snippet.
angular.module('app-1', ['ngRoute'])
.config(($routeProvider) => {
$routeProvider
.when('/BI/Zero', {
template: '<zero-panel class="flex-basis-half marginBottom" panel-name="SalesSnapshot", container-id="sales-snapshot"></zero-panel>',
resolve: {
DataService: (DataService) => {
return DataService.tryGetData();
},
},
});
})
.service('zero-data-service', DataService)
...
and I added ng-view directive to one in BI.aspx
There's NO error in browser, <zero-panel> is not rendered and tryGetDate() is not called too. I found someone said the 'path' defined to when() is part of the URL after the # symbol. Could you verify if this is true?
In terms other solution, the most intuitive thing I can think of is broadcasting an event when data service has obtained the data, and components listen to event to fetch the data, instead of fetching during their initialization.
I appreciate if anyone can suggest if $routeProvider would work in my usecase, or suggest any other possible solution to achieve the goal.

URL is showing MVC controller name twice

I am working with .net mvc and angular js.
HomeController
public ActionResult Index()
{
return View();
}
In angular controller there is a service to get all the item details
[HttpGet]
public JsonResult GetItemDetails()
{
// return item list
}
If I run the solution without opening HomeController in visual studio I found 404 error saying that resource is not found.
One more thing I have noticed in network is the called URL is "/Home/Home/GetItemDetails".
But when I open HomeController in visual studio everything will be working fine.
How it is happening ?
Make sure your routing values and the URL you called are one and the same.
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
For this, the URL looks like /Home/Index
Verify the URL you are using in Angular and you may be adding the controller name twice somewhere.
$http.get("Home/GetItemDetails") this is the URL I had used to call controller method.
I missed slash in this
So the solution is $http.get("/Home/GetItemDetails")
Thank you.

Routing in SPA with ASP.NET MVC 6 and AngularJS

I have a sample MVC6 single page app with one view in which I want to load 2 Angular partials using ngRoute. You can have a look at it at GitHub
There are 3 URLs in the app:
localhost - Index.cshtml
localhost/games - Index.cshtml with Angular's gamelist.html partial
localhost/games/2 - Index.cshtml with Angular's game.html partial
The routes config is the following:
MVC:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}");
routes.MapRoute("gamelist", "games", new { controller = "Home", action = "Index"});
routes.MapRoute("gameWithId", "games/2", new { controller = "Home", action = "Index" });
});
Angular:
myApp.config(['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
$routeProvider
.when('/games', {
templateUrl: 'partials/gameslist.html',
controller: 'GameController',
controllerAs: 'ctrl'
})
.when('/games/:gameId', {
templateUrl: 'partials/game.html',
controller: 'GameController',
controllerAs: 'ctrl'
});
$locationProvider.html5Mode(true);
}]);
It all works perfectly fine as long as I start the app from the home page '/' and then navigate to the partials using the links on the page. The problem is that the URL #3 (localhost/games/2) does not work if I start the app from it, by typing it in the address bar. The URL #2 (/games/) does work.
The reason why #3 does not work is that MVC removes '/games' part from the URL and what Angular gets is just '/2'. If you run the sample app, you will see that '$location.path = /2'. Of course Angular cannot map using that path and no partial is rendered. So my question is - how to make MVC return the full path to the client so that Angular can map it?
You can get it to work with HTML5 mode, you just need to ensure that every request maps back to your Index.cshtml view. At that point the AngularJS framework loads, client-side routing kicks in and evaluates the request URI and loads the appropriate controller and view.
We've done this with multiple Angular apps inside MVC with different .cshtml pages, though we use attribute routing with the wildcard character, e.g.
[Route("{*anything}")]
public ActionResult Index()
{
return View("Index");
}
The wildcard operator (*) tells the routing engine that the rest of the URI should be matched to the anything parameter.
I haven't had chance to get to grips with MVC6 yet but I think you can do something like this with the "new" version of attribute routing?
[HttpGet("{*anything:regex(^(.*)?$)}"]
public ActionResult Index()
{
return View("Index");
}
To make link #3 work from the browser's address bar, I turned off "html5Mode" in Angular and made links #-based.
kudos to this blog
I think it is a better solution.
His solution is rewriting the request that doesn't fit to any route and doesn't have any extension to the landing page of angular.
Here is the code.
public class Startup
{
public void Configure(IApplicationBuilder app, IApplicationEnvironment environment)
{
// Route all unknown requests to app root
app.Use(async (context, next) =>
{
await next();
// If there's no available file and the request doesn't contain an extension, we're probably trying to access a page.
// Rewrite request to use app root
if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
{
context.Request.Path = "/app/index.html"; // Put your Angular root page here
await next();
}
});
// Serve wwwroot as root
app.UseFileServer();
// Serve /node_modules as a separate root (for packages that use other npm modules client side)
app.UseFileServer(new FileServerOptions()
{
// Set root of file server
FileProvider = new PhysicalFileProvider(Path.Combine(environment.ApplicationBasePath, "node_modules")),
// Only react to requests that match this path
RequestPath = "/node_modules",
// Don't expose file system
EnableDirectoryBrowsing = false
});
}
}

Unified MVC Controller Routing in Asp.Net 5

During my working in ASP.NET 5, I am confused in some of the unified controller concepts. Please show me what I doing wrong.
In ASP.NET 5 the same controller is used for MVC and WebApi with only the difference of Routing attribute defined above a Web Api controller. My thoughts is that the "Route" attribute is only used to define a route. But there has been some occurances that defining "Route" attribute at the top is specifing a controller to be a Web Api controller.
In Startup.cs, I have the following routing configurations.
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapWebApiRoute(
"DefaultApi",
"api/{controller}/{id?}");
});
In Home Controller, there is "HttpGet" action for accepting WebApi get request, but it cannot be called without "Route" attribute, however the routing for the web api is already defined in the startup class.
public class HomeController : Controller {
public IActionResult Index() {
return View();
}
//[Route("api/[controller]")]
[HttpGet]
public IEnumerable<string> Get() {
return new string[] { "value1", "value2" };
}
}
And in Values controller, "About" action can't be accessed as the call to "/values/about" will give an error not found. And "/api/values/about" will redirected to the "Get" action.
[Route("api/[controller]")]
public class ValuesController : Controller {
public IActionResult About() {
ViewData["Message"] = "Your application description page.";
return View();
}
// GET: api/values
[HttpGet]
public IEnumerable<string> Get() {
return new string[] { "value1", "value2" };
}
}
But although I have specified the web api route in startup.cs file, I cant access the "/api/home" path.
What am I doing wrong?
I believe you are mixing different routing methods.
Because the controllers are unified, you do not have to specify a route for webapi and another one for MVC.
The MapWebApiRoute function is only for backwards compatibility with code written for the older WebApi 2 ASP.NET.
If you want to use the convention based routing, you can just leave the UseMvc empty like this:
app.UseMvc();
For a thorough explanation of the different options, please see this useful blogpost: ASP.NET 5 Deep Dive: Routing.
Also see this question about the non-existing differences between MVC and WebApi Controllers in ASP.NET 5.

Calling server side methods using ajax inside a custom extension

I am creating a custom extension for DNN using Christoc's DotNetNuke Module and Theme Development Template
I want to call server side methods using ajax inside my javascript file
example code
$.ajax({
url:'myModule/listOfPosts'
success:function() {
// do something with list of posts
}
});
How can i do this? I am open to all suggestions. Thank You
I did a tutorial on exactly this topic. Building DNN framework services for your module and then calling them securely using jquery ajax.
Client-centric module development
Also, I have another example on my website:
Rich-client Module with Knockout
You can also do it using AjaxPro.dll.
Here is the codeplex URL: AjaxPro 2
Design Section:
<asp:CheckBox ID="chkDelete" runat="server" onclick="javascript:deleteRecord();" />
Javascript:
<script type="text/javascript">
function deleteRecord() {
Modules.MyAdmin.ViewCars.deleteCar(); // asynchronous call
}
Code Behind:
namespace Modules.MyAdmin
{
public partial class ViewCars : PortalModuleBase
{
[AjaxPro.AjaxMethod]
public void deleteCar()
{
// Write your action over here
}
}
}

Resources