Building a Web App with ASP.NET Core, MVC 6, EF Core, and Angular
TOC
- Intro
- ASP.NET Core
- HTML and CSS Basics
- JavaScript
- MVC 6
- Bootstrap
- EF Core
- Creating the API
- AngularJS
Intro
ASP.NET Core
For yeoman, node and npm must be installed, nodejs.org. The command line VS code hello world app has .xproj extension for the project.
code .
node -- version
npm -- version
npm install yo -g
yo
cls
npm install generator-* (aspnet) -g
yo aspnet
new
new project options
web fwk | os fwk | title |
---|---|---|
ASP.NET | .NET Framework 4.5-4.7 | ASP.NET WebForms, MVC, WebAPI, classic |
ASP.NET Core | .NET Core | ASP.NET Core web app multiplatform |
ASP.NET Core | .NET Framework | ASP.NET Core web app for Windows |
HTML and CSS Basics
Start Without Debugging - ctrl + f5. create new file - shift + f2. Content below named middleware.
public void Configure (IApplicationBuilder app) {
app.Run ( async (ctx) => { await ctx.Response.WriteAsync ("some html"); });
}
Move static html to www root folder, similar to the way that Node and Ruby and Rails and even JSP Pages handles it. In ASP.NET Core, every feature of the web server is optional. This piece of middleware is called app.UseDefaultFiles()
. This will add some standard default files so that when looking in a directory, and that includes the root folder of your project, that it’s going to automatically look for an index.html. Package Microsoft.AspNetCore.StaticFiles
must be added.
Web Essentials 2013 has a Surround with tag… feature (Alt+Shift+W) that is more fluid than the built-in Surround with.
overriding input buttons, default is text input[type=submit] {width: auto;}
overriding input tags input[type=text], input[type=password], textarea {width: 150px;}
by default a label is inline, this will move to the row below label {display: block;}
images inside sidebar div #sidebar img {max-width: 50px;}
, instead provide a class for the small images .headshot {max-width: 50px;}
active list items under menu are bold .menu li {list-style-type: none;} .menu li.active {font-weight: bold;}
the box model
create a sidebar and a wrapper div over main and footer
#sidebar {margin: 0; position: fixed; overflow: hidden; height:100%; width: 200px; left:0;}
#wrapper {margin: 0 0 0 200px;}
#footer {bottom: 0; width: 100%;}
JavaScript
- The client side language for web apps
- object oriented
- prototypical inheritance instead of classes
- dynamically typed
- just in time compiled without an intermediate format, like MSIL or Bytecode
- older browsers have an interpreter instead of JIT
- place scripts before the end of the body tag, executes as html is parsed
events / anonymous function / callback
var main = document.getElementById("main");
main.onmouseenter = function () { main.style.backgroundcolor = "#ddd"; };
main.onmouseleave = function () { main.style.backgroundcolor = ""; main.style = "background: #eee"; };
global scope, as files are loaded they may overwrite variables, avoid name collisions. Either use very specific names, or use function private scope. The function name though may be leaking other function names, immediately executing nameless function. (function(){})();
Bower is for js what NuGet is for cs, client side package management. Add Bower configuration file, dependencies download to a lib folder in wwroot. .bowerrc is the config options for bower.
JQuery handles the cross-browser compatibility issues. <script type="text/javascript" src="lib/jquery/dist/jquery.min.js"></script>
$('#username').text("was innerHtml or InnerHTML");
$(#main).on("mouseenter", function() {main.style = ""} );
var menuItems = $("ul.menu li a");
menuItems.on("click", function(){ alert($(this).text()); });
div, sidebar toggle visibility button, wrapped set of DOM elements
var hscn = "hide-sidebar";
$("#sidebar-toggle").on("click", function () {
var sw = $("#sidebar,#wrapper");
sw.toggleClass(hscn);
if (sw.hasClass(hscn)) {
$(this).text("show sidebar");
} else {
$(this).text("hide sidebar");
}
});
On the css side #sidebar.hide-sidebar {left: -250px;}
downside is tracking const values, and #wrapper.hide-sidebar {margin-left: 0;}
. Can add a transition: left ease .25s;
and transition: margin-left ease .25s;
. Also add vendor specifics.
MVC 6
Add package _Microsoft.AspNet.Mvc.ViewFeatures_
for resolving the Controller
class. That’s just a subset of MVC, so better edit project.json
to add a dependency for Microsoft.AspNetCore.Mvc
, then add a using namespace. public IActionResult Index() {return View(); }
. Add a cshtml view for the index @{ ViewBag.Title = ""}
. In Startup.cs, enable MVC6.
public void Configure (IApplicationBuilder app) {
app.UseStaticFiles();
app.UseMvc(cfg=>{
cfg.MapRoute(name: "Default", template: "{controller}/{action}/{id?}"),
defaults: new {controller="App", action="Index"} );
});
}
public void ConfigureServices (IServiceCollection services) {
services.AddMvc();
}
Add a MVC View layout page as Views\Shared_Layout.cshtml. The sidebar, header and footer from index will be moved to the shared layout. Then link the layout to the Razor index view as @{Layout="Layout.cshtml"}
. Add MVC View Start Page as ViewStart.cshtml in the actual views folder. This will isolate the layout include boilerplate. Obviously, paths to css and js will have to be revised as to site root as ~. Also use @RenderBody()
in the layout.
Add app.UseDeveloperExceptionPage()
in Startup.Configure, diagnostics include stack trace, want to hide that from end users, keep it for staging and test servers, instead of #if DEBUG
, add the IHostingEnvironment
parameter and check env.IsProduction()
or env.IsEnvironment("Development")
. Project properties has environment variables defined, like ASPNETCORE_ENVIRONMENT
.
Use tag helpers to modify hrefs for main menu, from <a href="/app/about">About</a>
to something like <a asp-controller="App" asp-action="About">About</a>
, will generate hrefs programatically while rendering on the server. To enable tag helpers, add a dependency to Microsoft.AspNet.Mvc.TagHelpers
. Wire it up in layout.cshtml as @inject IHostingEnvironment env
or add a _MVC View Imports Page. @addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
this will inject tag helpers into all the views needed. Also include common namespaces like @using ProjectNS.Models
.
View models.
Validation attributes. [Required]
, [StringLength
(MinimumLength=10,MaximumLength=4096)]
. Also add jquery-validation: "~1.15.0"
and jquery-validation-unobtrusive: "~3.2.6"
to bower.json
. Add @RenderSection("scripts", false)
in the layout. In the contacts page define the scripts section, and use it like <span asp-validation-for="Name" />
and <span asp-validation-summary="ModelOnly"></span>
.
@section scripts {
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>
}
Posts. <form method="post">
. Add [HttpPost]
with a ContactViewModel
parameter, model binding is automatic through the controller. Services/IMailService.cs. Implement a mock debug object for the service interface. Debug.WriteLine($"Send mail to {to} from {from} with subject {subject} and body {body}");
.
Use dependency injection for service implementation. Add constructor to AppController
. Call the service on the post action. Activation in Startup ConfigureServices
: services.AddTransient<IMailService, DebugMailService>();
or services.AddScoped<IMailService, MockMailService>();
. Transient creates on the fly and caches it. Scoped creates one for each set of requests. Add a IHostingEnvironment
parameter to the Startup
constructor. var builder = new ConfigurationBuilder().SetBasePath(_env.ContentRootPath).AddJsonFile("config.json").AddEnvironmentVariables(); _config = builder.Build();
returns an IConfigurationRoute
, a sort of name value bag, like _config[MailSettings:ToAddress]
. services.AddSingleton(_config);
. State of the data passed into the view, ModelState.IsValid
, ModelState.AddModelError("Email","Message")
, ‘ModelState.Clear()’ upon successful post.
Bootstrap
- open source fwk for web apps
- composed of CSS and LESS, LESS is a language for writing program-like facilities in CSS
- includes JavaScript components to help you do some common features like carousels, styling of buttons
- modular and skinnable which means you can use the components you want out of Bootstrap, as well as being able to change the way that Bootstrap looks and feels
- help you solve the 80% of common design metaphors you’re going to see in your web applications. So a bar for navigation through the website, breadcrumbs, panels, thumbnail images
VS add Bootstrap dependency in bower.json
. Link bootstrap.min.css
stylesheet site wide and the minified js also. Also "font-awesome": "~4.4.0"
. Toggle arrow left <i class="glyphicon glyphicon-chevron-left"></i>
. Refactor to <i class="fa fa-angle-left"></i>
. Change impl in site.js
for the auto function hide/show.
Bootswatch is a list of templates that modify look and feel over Bootstrap. Another bower call, "bootswatch": "3.3.5+2"
, Bootswatch v2 over the Bootstrap release. <link rel="stylesheet" href="~/lib/bootswatch/spacelab/bootstrap.min.css" />
. Navbar: Move menu
class to nav
in the sidebar section. <nav class="navbar navbar-inverse">
over <button id="sidebarToggle">
. If bar contains a list, style as <ul class="nav navbar-nav navbar-left">
.
Default font icon buttons.
Bootstrap grid system is a world of 12 columns. The label and input group is a form-group and decorate the active ctrl with form-control.
Bootstrap 4. alpha phase in sep 2015. modest change compared to 2 to 3 transition. Card replaces Panels and Thumbnails.
EF Core
- build data access without the requirement on relational databases?
- EF6 will work if you need maturity, work in progress
- use migrations to build the db for you
- seeding not built-in, but easily doable
- repository pattern for testable data access
Models folder has dtos, in comparison with the ViewModels folder. Code first, auto implemented property only classes. Use ICollection
instead of read-only IEnumerable
objects. Access to the db, derive from EFCore.DbContext
. For entities, use DbSet<TEntity>
. Add services.AddDbContext<CustomContext>();
in ConfigureServices
. Additional parameter for controller constructor is the db context. database provider must be configured, override DbContext.OnConfiguring
by calling base
and optionsBuilder.UseSqlServer(_config["CS:CONN"]);
.
alt+space opens up a console window. dotnet ef migrations add InitialDatabase
. A new folder appears, Migrations
. Or dotnet database ef update
to create the schema. To add sample data, add a ContextSeedData
db ctx wrapper, with async Task Ensure() { if(!_ctx.Trips.Any()) {CrUd();} }
. services.AddTransient<ContextSeedData>();
, have to explictly call in Configure, param.EnsureSeedData().Wait()
.
Repository. services.AddScoped<WorldRepository, IWorldRepository>();
. Every time a custom context instance is used as a parameter, replace with the repository interface. services.AddLogging()
and ILoggerFactory.AddDebug(LogLevel.Information)
. In the controller constructor add MS.Extentions.ILogger<TController>
.
Creating the API
return json in a controller
[HttpGet("api/trips")]
public JsonResult Get() { return Json (new Trip() { Name = "Some trip" }); }
get errors too
[HttpGet("api/trips")]
public IActionResult Get() {
// return BadRequest ("404");
// return Ok (new Trip() { Name = "Some trip" });
//return Ok (_repo.GetAllTrips());
return Ok(Mapper.Map<IEnumerable<TripViewModel>>(results));
}
Rest Client: postman.
Move from Pascal to Camel casing
services.AddMvc()
.AddJsonOptions (config => config.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver());
post with object model bind, one can add default class route for controller [Route("api/trips")]
at class level.
[HttpPost("api/trips")]
// public IActionResult Post ([FromBody] Trip t) {
public IActionResult Post ([FromBody] TripViewModel tvm) {
var t = Mapper.Map<Trip>(tvm);
if (ModelState.IsValid) return Created ($"api/trips/{t.Name}", t);
return BadRequest (t.Name);
}
Add validation attributes to the model data classes / view model classes, [Required]
, [StringLength]
.
AutoMapper. Add package to project.json
. Initialize this in Configure, and use it like above.
Mapper.Initialize (config => config.CreateMap<TripViewModel, Trip>() );
try-ok-catch-log-badrequest. use a ILogger<TController>
.
AngularJS
- supplies services usable throughout your project
- add anjular 1.5.7 to bower.json
- include in a cshtml view
@section Scripts {<script src="~/lib/angular/angular.min.js"></script>}
- NG app attribute
data-ng-*
andng-*
, e.g.<div class="row" ng-app="app-trips" />
- evaluate expressions
<div> </div>
- code written in app-controller.js , use it like
<div ng-controller="tripsController as vm"></div>
. - table ->
tr ng-repeat="t in vm.trips"
->td