In this article by Valerio De Sanctis, author of the book ASP.NET Web API and Angular 2, we will explore the client-server interaction capabilities of our frameworks: to put it in other words, we need to understand how Angular2 will be able to fetch data from ASP.NET Core using its brand new, MVC6-based API structure. We won't be worrying about how will ASP.NET core retrieve these data – be it from session objects, data stores, DBMS, or any possible data source, that will come later on. For now, we'll just put together some sample, static data in order to understand how to pass them back and forth by using a well-structured, highly-configurable and viable interface.
(For more resources related to this topic, see here.)
The data flow
A Native Web App following the single-page application approach will roughly handle the client-server communication in the following way:
In case you are wondering about what these Async Data Requests actually are, the answer is simple, everything, as long as it needs to retrieve data from the server, which is something that most of the common user interactions will normally do, including (yet not limiting to), pressing a button to show more data or to edit/delete something, following a link to another app view, submitting a form and so on.
That is, unless the task is so trivial or it involves a minimal amount of data that the client can entirely handle it, meaning that it already has everything he needs. Examples of such tasks are, show/hide element toggles, in-page navigation elements (such as internal anchors), and any temporary job requiring to hit a confirmation or save button to be pressed before being actually processed.
The above picture shows, in a nutshell, what we're going to do. Define and implement a pattern to serve these JSON-based, server-side responses our application will need to handle the upcoming requests. Since we've chosen a strongly data-driven application pattern such as a Wiki, we'll surely need to put together a bunch of common CRUD based requests revolving around a defined object which will represent our entries. For the sake of simplicity, we'll call it Item from now on.
These requests will address some common CMS-inspired tasks such as: display a list of items, view/edit the selected item's details, handle filters, and text-based search queries and also delete an item.
Before going further, let's have a more detailed look on what happens between any of these Data Request issued by the client and JSON Responses send out by the server, i.e. what's usually called the Request/Response flow:
As we can see, in order to respond to any client-issued Async Data Request we need to build a server-side MVC6 WebAPIControllerfeaturing the following capabilities:
Read and/or Write data using the Data Access Layer.
Organize these data in a suitable, JSON-serializableViewModel.
Serialize the ViewModel and send it to the client as a JSON Response.
Based on these points, we could easily conclude that the ViewModel is the key item here. That's not always correct: it could or couldn't be the case, depending on the project we are building. To better clarify that, before going further, it could be useful to spend a couple words on the ViewModel object itself.
The role of the ViewModel
We all know that a ViewModel is a container-type class which represents only the data we want to display on our webpage. In any standard MVC-based ASP.NET application, the ViewModel is instantiated by the Controller in response to a GET request using the data fetched from the Model: once built, the ViewModel is passed to the View, where it is used to populate the page contents/input fields.
The main reason for building a ViewModel instead of directly passing the Model entities is that it only represents the data that we want to use, and nothing else. All the unnecessary properties that are in the model domain object will be left out, keeping the data transfer as lightweight as possible. Another advantage is the additional security it gives, since we can protect any field from being serialized and passed through the HTTP channel.
In a standard Web API context, where the data is passed using RESTFul conventions via serialized formats such as JSON or XML, the ViewModel could be easily replaced by a JSON-serializable dynamic object created on the fly, such as this:
var response = new{
Id = "1",
Title = "The title",
Description = "The description"
};
This approach is often viable for small or sample projects, where creating one (or many) ViewModel classes could be a waste of time. That's not our case, though, conversely, our project will greatly benefit from having a well-defined, strongly-typed ViewModel structure, even if they will be all eventually converted into JSON strings.
Our first controller
Now that we have a clear vision of the Request/Response flow and its main actors, we can start building something up. Let's start with the Welcome View, which is the first page that any user will see upon connecting to our native web App. This is something that in a standard web application would be called Home Page, but since we are following a Single Page Application approach that name isn't appropriate. After all, we are not going to have more than one page.
In most Wikis, the Welcome View/Home Page contains a brief text explaining the context/topic of the project and then one or more lists of items ordered and/or filtered in various ways, such as:
The last inserted ones (most recent first).
The most relevant/visited ones (most viewed first).
Some random items (in random order).
Let's try to do something like that. This will be our master plan for a suitable Welcome View:
In order to do that, we're going to need the following set of API calls:
api/items/GetLatest (to fetch the last inserted items).
api/items/GetMostViewed (to fetch the last inserted items).
api/items/GetRandom (to fetch the last inserted items).
As we can see, all of them will be returning a list of items ordered by a well-defined logic. That's why, before working on them, we should provide ourselves with a suitable ViewModel.
The ItemViewModel
One of the biggest advantages in building a Native Web App using ASP.NET and Angular2 is that we can start writing our code without worrying to much about data sources: they will come later, and only after we're sure about what we really need. This is not a requirement either - you are also free to start with your data source for a number of good reasons, such as:
You already have a clear idea of what you'll need.
You already have your entity set(s) and/or a defined/populated data structure to work with.
You're used to start with the data, then moving to the GUI.
All the above reasons are perfectly fine: you won't ever get fired for doing that. Yet, the chance to start with the front-end might help you a lot if you're still unsure about how your application will look like, either in terms of GUI and/or data. In building this Native Web App, we'll take advantage of that: hence why we'll start defining our Item ViewModelinstead of creating its Data Source and Entity class.
From Solution Explorer, right-click to the project root node and add a new folder named ViewModels. Once created, right-click on it and add a new item: from the server-side elements, pick a standard Class, name it ItemViewModel.cs and hit the Add button, then type in the following code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespaceOpenGameListWebApp.ViewModels
{
[JsonObject(MemberSerialization.OptOut)]
publicclassItemViewModel
{
#region Constructor
public ItemViewModel()
{
}
#endregion Constructor
#region Properties
publicint Id { get; set; }
publicstring Title { get; set; }
publicstring Description { get; set; }
publicstring Text { get; set; }
publicstring Notes { get; set; }
[DefaultValue(0)]
publicint Type { get; set; }
[DefaultValue(0)]
publicint Flags { get; set; }
publicstring UserId { get; set; }
[JsonIgnore]
publicintViewCount { get; set; }
publicDateTime CreatedDate { get; set; }
publicDateTime LastModifiedDate { get; set; }
#endregion Properties
}
}
As we can see, we're defining a rather complex class: this isn't something we could easily handle using dynamic object created on-the-fly, hence why we're using a ViewModel instead.
We will be installing Newtonsoft's Json.NET Package using NuGet. We will start using it in this class, by including its namespace in line 6 and decorating our newly-created Item class with a JsonObject Attribute in line 10. That attribute can be used to set a list of behaviours of the JsonSerializer / JsonDeserializer methods, overriding the default ones: notice that we used MemberSerialization.OptOut, meaning that any field will be serialized into JSON unless being decorated by an explicit JsonIgnore attribute or NonSerializedattribute. We are making this choice because we're going to need most of our ViewModel properties serialized, as we'll be seeing soon enough.
The ItemController
Now that we have our ItemViewModel class, let's use it to return some server-side data. From your project's root node, open the /Controllers/ folder: right-click on it, select Add>New Item, then create a Web API Controller class, name it ItemController.cs and click the Add button to create it.
The controller will be created with a bunch of sample methods: they are identical to those present in the default ValueController.cs, hence we don't need to keep them. Delete the entire file content and replace it with the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
usingOpenGameListWebApp.ViewModels;
namespaceOpenGameListWebApp.Controllers
{
[Route("api/[controller]")]
publicclassItemsController : Controller
{
// GET api/items/GetLatest/5
[HttpGet("GetLatest/{num}")]
publicJsonResult GetLatest(int num)
{
var arr = newList<ItemViewModel>();
for (int i = 1; i <= num; i++) arr.Add(newItemViewModel() {
Id = i,
Title = String.Format("Item {0} Title", i),
Description = String.Format("Item {0} Description", i)
});
var settings = newJsonSerializerSettings() {
Formatting = Formatting.Indented
};
returnnewJsonResult(arr, settings);
}
}
}
This controller will be in charge of all Item-related operations within our app.
As we can see, we started defining a GetLatestmethod accepting a single Integerparameter value.The method accepts any GET request using the custom routing rules configured via the HttpGetAttribute: this approach is called Attribute Routing and we'll be digging more into it later in this article. For now, let's stick to the code inside the method itself.
The behaviour is really simple: since we don't (yet) have a Data Source, we're basically mocking a bunch of ItemViewModel objects: notice that, although it's just a fake response, we're doing it in a structured and credible way, respecting the number of items issued by the request and also providing different content for each one of them.
It's also worth noticing that we're using a JsonResult return type, which is the best thing we can do as long as we're working with ViewModel classes featuring the JsonObject attribute provided by the Json.NET framework: that's definitely better than returning plain string or IEnumerable<string> types, as it will automatically take care of serializing the outcome and setting the appropriate response headers.Let's try our Controller by running our app in Debug Mode: select Debug>Start Debugging from main menu or press F5. The default browser should open, pointing to the index.html page because we did set it as the Launch URL in our project's debug properties. In order to test our brand new API Controller, we need to manually change the URL with the following:
/api/items/GetLatest/5
If we did everything correctly, it will show something like the following:
Our first controller is up and running. As you can see, the ViewCount property is not present in the Json-serialized output: that's by design, since it has been flagged with the JsonIgnore attribute, meaning that we're explicitly opting it out.
Now that we've seen that it works, we can come back to the routing aspect of what we just did: since it is a major topic, it's well worth some of our time.
Understanding routes
We will acknowledge the fact that the ASP.NET Core pipeline has been completely rewritten in order to merge the MVC and WebAPI modules into a single, lightweight framework to handle both worlds. Although this certainly is a good thing, it comes with the usual downside that we need to learn a lot of new stuff. Handling Routes is a perfect example of this, as the new approach defines some major breaking changes from the past.
Defining routing
The first thing we should do is giving out a proper definition of what Routing actually is.
To cut it simple, we could say that URL routing is the server-side feature that allows a web developer to handle HTTP requests pointing to URIs not mapping to physical files. Such technique could be used for a number of different reasons, including:
Giving dynamic pages semantic, meaningful and human-readable names in order to advantage readability and/or search-engine optimization (SEO).
Renaming or moving one or more physical files within your project's folder tree without being forced to change their URLs.
Setup alias and redirects.
Routing through the ages
In earlier times, when ASP.NET was just Web Forms, URL routing was strictly bound to physical files: in order to implement viable URL convention patterns the developers were forced to install/configure a dedicated URL rewriting tool by using either an external ISAPI filter such as Helicontech's SAPI Rewrite or, starting with IIS7, the IIS URL Rewrite Module.
When ASP.NET MVC got released, the Routing pattern was completely rewritten: the developers could setup their own convention-based routes in a dedicated file (RouteConfig.cs, Global.asax, depending on template) using the Routes.MapRoute method. If you've played along with MVC 1 through 5 or WebAPI 1 and/or 2, snippets like this should be quite familiar to you:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
This method of defining routes, strictly based upon pattern matching techniques used to relate any given URL requests to a specific Controller Actions, went by the name of Convention-based Routing.
ASP.NET MVC5 brought something new, as it was the first version supporting the so-called Attribute-based Routing. This approach was designed as an effort to give to developers a more versatile approach. If you used it at least once you'll probably agree that it was a great addition to the framework, as it allowed the developers to define routes within the Controller file. Even those who chose to keep the convention-based approach could find it useful for one-time overrides like the following, without having to sort it out using some regular expressions:
[RoutePrefix("v2Products")]
publicclassProductsController : Controller
{
[Route("v2Index")]
publicActionResult Index()
{
return View();
}
}
In ASP.NET MVC6, the routing pipeline has been rewritten completely: that's way things like the Routes.MapRoute() method is not used anymore, as well as any explicit default routing configuration. You won't be finding anything like that in the new Startup.cs file, which contains a very small amount of code and (apparently) nothing about routes.
Handling routes in ASP.NET MVC6
We could say that the reason behind the Routes.MapRoute method disappearance in the Application's main configuration file is due to the fact that there's no need to setup default routes anymore. Routing is handled by the two brand-new services.AddMvc() and services.UseMvc() methods called within the Startup.cs file, which respectively register MVC using the Dependency Injection framework built into ASP.NET Core and add a set of default routes to our app.
We can take a look at what happens behind the hood by looking at the current implementation of the services.UseMvc()method in the framework code (relevant lines are highlighted):
public static IApplicationBuilder UseMvc(
[NotNull] this IApplicationBuilder app,
[NotNull] Action<IRouteBuilder> configureRoutes)
{
// Verify if AddMvc was done before calling UseMvc
// We use the MvcMarkerService to make sure if all the services were added.
MvcServicesHelper.ThrowIfMvcNotRegistered(app.ApplicationServices);
var routes = new RouteBuilder
{
DefaultHandler = new MvcRouteHandler(),
ServiceProvider = app.ApplicationServices
};
configureRoutes(routes);
// Adding the attribute route comes after running the user-code because
// we want to respect any changes to the DefaultHandler.
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(
routes.DefaultHandler,
app.ApplicationServices));
return app.UseRouter(routes.Build());
}
The good thing about this is the fact that the framework now handles all the hard work, iterating through all the Controller's actions and setting up their default routes, thus saving us some work. It worth to notice that the default ruleset follows the standard RESTFulconventions, meaning that it will be restricted to the following action names:Get, Post, Put, Delete. We could say here that ASP.NET MVC6 is enforcing a strict WebAPI-oriented approach - which is much to be expected, since it incorporates the whole ASP.NET Core framework.
Following the RESTful convention is generally a great thing to do, especially if we aim to create a set of pragmatic, RESTful basedpublic API to be used by other developers. Conversely, if we're developing our own app and we want to keep our API accessible to our eyes only, going for custom routing standards is just as viable: as a matter of fact, it could even be a better choice to shield our Controllers against some most trivial forms of request flood and/or DDoS-based attacks. Luckily enough, both the Convention-based Routing and the Attribute-based Routing are still alive and well, allowing you to setup your own standards.
Convention-based routing
If we feel like using the most classic routing approach, we can easily resurrect our beloved MapRoute() method by enhancing the app.UseMvc() call within the Startup.cs file in the following way:
app.UseMvc(routes =>
{
// Route Sample A
routes.MapRoute(
name: "RouteSampleA",
template: "MyOwnGet",
defaults: new { controller = "Items", action = "Get" }
);
// Route Sample B
routes.MapRoute(
name: "RouteSampleB",
template: "MyOwnPost",
defaults: new { controller = "Items", action = "Post" }
);
});
Attribute-based routing
Our previously-shownItemController.cs makes a good use of the Attribute-Based Routing approach, featuring it either at Controller level:
[Route("api/[controller]")]
public class ItemsController : Controller
Also at Action Method level:
[HttpGet("GetLatest")]
public JsonResult GetLatest()
Three choices to route them all
Long story short, ASP.NET MVC6 is giving us three different choices for handling routes: enforcing the standard RESTful conventions, reverting back to the good old Convention-based Routing or decorating the Controller files with the Attribute-based Routing.
It's also worth noticing that Attribute-based Routes, if and when defined, would override any matchingConvention-basedpattern: both of them, if/when defined, would override the default RESTful conventions created by the built-in UseMvc() method.
In this article we're going to use all of these approaches, in order to learn when, where and how to properly make use of either of them.
Adding more routes
Let's get back to our ItemController. Now that we're aware of the routing patterns we can use, we can use that knowledge to implement the API calls we're still missing.
Open the ItemController.cs file and add the following code (new lines are highlighted):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using OpenGameListWebApp.ViewModels;
using Newtonsoft.Json;
namespaceOpenGameListWebApp.Controllers
{
[Route("api/[controller]")]
publicclassItemsController : Controller
{
#region Attribute-based Routing
///<summary>
/// GET: api/items/GetLatest/{n}
/// ROUTING TYPE: attribute-based
///</summary>
///<returns>An array of {n} Json-serialized objects representing the last inserted items.</returns>
[HttpGet("GetLatest/{n}")]
publicIActionResult GetLatest(int n)
{
var items = GetSampleItems().OrderByDescending(i => i.CreatedDate).Take(n);
return new JsonResult(items, DefaultJsonSettings);
}
/// <summary>
/// GET: api/items/GetMostViewed/{n}
/// ROUTING TYPE: attribute-based
/// </summary>
/// <returns>An array of {n} Json-serialized objects representing the items with most user views.</returns>
[HttpGet("GetMostViewed/{n}")]
public IActionResult GetMostViewed(int n)
{
if (n > MaxNumberOfItems) n = MaxNumberOfItems;
var items = GetSampleItems().OrderByDescending(i => i.ViewCount).Take(n);
return new JsonResult(items, DefaultJsonSettings);
}
/// <summary>
/// GET: api/items/GetRandom/{n}
/// ROUTING TYPE: attribute-based
/// </summary>
/// <returns>An array of {n} Json-serialized objects representing some randomly-picked items.</returns>
[HttpGet("GetRandom/{n}")]
public IActionResult GetRandom(int n)
{
if (n > MaxNumberOfItems) n = MaxNumberOfItems;
var items = GetSampleItems().OrderBy(i => Guid.NewGuid()).Take(n);
return new JsonResult(items, DefaultJsonSettings);
}
#endregion
#region Private Members
/// <summary>
/// Generate a sample array of source Items to emulate a database (for testing purposes only).
/// </summary>
/// <param name="num">The number of items to generate: default is 999</param>
/// <returns>a defined number of mock items (for testing purpose only)</returns>
private List<ItemViewModel> GetSampleItems(int num = 999)
{
List<ItemViewModel> lst = new List<ItemViewModel>();
DateTime date = new DateTime(2015, 12, 31).AddDays(-num);
for (int id = 1; id <= num; id++)
{
lst.Add(new ItemViewModel()
{
Id = id,
Title = String.Format("Item {0} Title", id),
Description = String.Format("This is a sample description for item {0}: Lorem ipsum dolor sit amet.", id),
CreatedDate = date.AddDays(id),
LastModifiedDate = date.AddDays(id),
ViewCount = num - id
});
}
return lst;
}
/// <summary>
/// Returns a suitable JsonSerializerSettings object that can be used to generate the JsonResult return value for this Controller's methods.
/// </summary>
private JsonSerializerSettings DefaultJsonSettings
{
get
{
return new JsonSerializerSettings()
{
Formatting = Formatting.Indented
};
}
}
#endregion
}
We added a lot of things there, that's for sure. Let's see what's new:
We added the GetMostViewed(n) and GetRandom(n) methods, built upon the same mocking logic used for GetLatest(n): either one requires a single parameter of Integer type to specify the (maximum) number of items to retrieve.
We added two new private members:
The GetLatestItems() method, to generate some sample Item objects when we need them. This method is an improved version of the dummy item generator loop we had inside the previous GetLatest() method implementation, as it acts more like a Dummy Data Provider: we'll tell more about it later on.
The DefaultJsonSettings property, so we won't have to manually instantiate a JsonSerializerSetting object every time.
We also decorated each class member with a dedicated<summary> documentation tag explaining what it does and its return value. These tags will be used by IntelliSense to show real-time information about the type within the Visual Studio GUI. They will also come handy when we'll want to generate an auto-generated XML Documentationfor our project by using industry-standard documentation tools such as Sandcastle.
Finally, we added some #region / #endregion pre-processor directives to separate our code into blocks. We'll do this a lot from now on, as this will greatly increase our source code readability and usability, allowing us to expand or collapse different sections/part when we don't need them, thus focusing more on what we're working on.
For more info regarding documentation tags, take a look at the following MSDN official documentation page:
https://msdn.microsoft.com/library/2d6dt3kf.aspx
If you want know more about C# pre-processor directives, this is the one to check out instead:
https://msdn.microsoft.com/library/9a1ybwek.aspx
The dummy data provider
Our new GetLatestItems() method deserves a couple more words. As we can easily see it emulates the role of a Data Provider, returning a list of items in a credible fashion. Notice that we built it in a way that it will always return identical items, as long as the num parameter value remains the same:
The generated items Id will follow a linear sequence, from 1 to num.
Any generated item will have incremental CreatedDate and LastModifiedDate values based upon their Id: the higher the Id, the most recent the two dates will be, up to 31 December 2015. This follows the assumption that most recent items will have higher Id, as it normally is for DBMS records featuring numeric, auto-incremental keys.
Any generated item will have a decreasing ViewCount value based upon their Id: the higher the Idis, the least it will be. This follows the assumption that newer items will generally get less views than older ones.
While it obviously lacksany insert/update/delete feature, this Dummy Data Provideris viable enough to serve our purposes until we'll replace it with an actual, persistence-based Data Source.
Technically speaking, we could do something better than we did by using one of the many Mocking Framework available through NuGet:Moq, NMock3,NSubstitute orRhino, just to name a few.
Summary
We spent some time into putting the standard application data flow under our lens: a two-way communication pattern between the server and their clients, built upon the HTTP protocol. We acknowledged the fact that we'll be mostly dealing with Json-serializable object such as Items, so we chose to equip ourselves with an ItemViewModel server-side class, together with an ItemController that will actively use it to expose the data to the client.
We started building our MVC6-based WebAPI interface by implementing a number of methods required to create the client-side UI we chose for our Welcome View, consisting of three item listings to show to our users: last inserted ones, most viewed ones and some random picks. We routed the requests to them by using a custom set of Attribute-based routing rules, which seemed to be the best choice for our specific scenario.
While we were there, we also took the chance to add a dedicated method to retrieve a single Item from its unique Id, assuming we're going to need it for sure.
Resources for Article:
Further resources on this subject:
Designing your very own ASP.NET MVC Application [article]
ASP.Net Site Performance: Improving JavaScript Loading [article]
Displaying MySQL data on an ASP.NET Web Page [article]
Read more