Building a Web App with ASP.NET Core, MVC 6, EF Core, and Angular

TOC

Intro

1
2 3

ASP.NET Core

principles three frameworks
completely composed open web
OVERVIEW
new project CLR
sln explorer program

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().

Demo repo.

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-* and ng-*, 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