Introduction
I’ve been a developer for .NET Windows Forms, WPF and background services for many years, but I’ve always been a denier of web development. Not that I haven’t tried it and even developed some small web applications, but I’ve always found it cumbersome and not intuitive.
Some time ago, I developed a kiosk type application with AngularJS and NodeJS on a Raspberry Pi, which brought me to the idea of doing something similar in Windows with ASP.NET.
The following is a very special how-to for developers with programming experience, who want to start with web. Wait, probably this is nothing for you. At least, it has been something for me.
Visual Studio
I am using Visual Studio 2015 Update 3 and wanted to check what was the best way for me to create a small Web application.
The VS templates are very bloated and create a great amount of boilerplate code. In addition, I wanted to understand how all the details work together.
So I’ve built a sample Web application and demonstrate the steps here.
Why I’ve avoided using NuGet packages for Angular, or using Bower or …
Yes, I know that there are some tools out there which can help organizing all the packages, structure your development and help you keep track of the application.
But my main goal was to focus on the basics.
And not only that: I’ve been trying NuGet for adding the needed client files. But if you want a certain directory structure, you have to manually change this. In the end, this is more work than it is worth.
Why self-hosting and not using IIS ?
IIS is a big monster and needs a great amount of configuration done. In practice, it is often questionable if this has to be done by the administration or the developer. In addition, I’ve wanted to have some tasks done periodically in the background, which is easy with a Windows service. This may be a personal preference too, but I really like it to be done with a Windows service, which can be installed very easily and does not need fiddling around.
Why not a well-proven structure in the first place ?
During the development of a bit larger project than my first one, I actually used controller and model folders and made some improvements. Nevertheless, I will stay with a very simple approach here. There was the idea to create a very basic and minimal example as the first example, and then use a better structure for the following web projects.
Developing steps
♦ We are starting with the creation of a Windows service:
https://www.rsprog.de/windowsserviceselfinstall/
♦ Use framework 4.5.2
♦ In the project tree, right click on the project ==> Manage NuGet Packages…
♦ Browse ==> Enter “Microsoft.AspNet.WebApi.OwinSelfHost” and search
♦ Install Microsoft.AspNet.WebApi.OwinSelfHost
♦ Browse ==> Enter “Microsoft.Owin.StaticFiles” and search
♦ Install Microsoft.Owin.StaticFiles
♦ In the project tree, right click on the project ==> Add ==> New folder: Content
♦ A directory has been created in the filesystem. Now add some files to it:
♦ Download AngularJS
♦ Branch 1.7.x (latest)
♦ Build Minified
♦ Download
♦ Put angular.min.js in the Content directory
♦ https://angular-ui.github.io/bootstrap/
♦ Download (2.5.0)
♦ Build Minified
♦ Include Templates Yes
♦ Download
♦ Put ui-bootstrap-tpls-2.5.0.min.js in the Content directory
♦ https://getbootstrap.com/docs/3.3/
♦ Download Bootstrap ==> Download Bootstrap
♦ We need these files from the ZIP:
bootstrap-3.3.7-dist\css\bootstrap.min.css
bootstrap-3.3.7-dist\fonts\*
♦ Put bootstrap.min.css in the Content directory
♦ Put the fonts directory in the Content directory
♦ https://unpkg.com/ng-table@3.0.1/bundles/
From there, copy
ng-table.min.css
ng-table.min.js
directly into the Content directory
The next one is not really a must, but I found this to be useful for nice looking checkboxes and radio buttons (I don’t like the original bootstrap one’s).
♦ https://github.com/flatlogic/awesome-bootstrap-checkbox
♦ Download ZIP and extract awesome-bootstrap-checkbox.css
Put awesome-bootstrap-checkbox.css in the Content directory
♦ In Windows explorer, select all files and the fonts dir and drag them into the Content folder in the Solution Explorer
♦ VS main menu Project ==> Show All Files
♦ In Solution Explorer:
♦ Select all files in Content\fonts folder:
♦ Right click ==> Include In Project
♦ In Properties view:
♦ Build Action: Content
♦ Copy to Output Directory: Copy if newer
♦ Select the other files from Content directory
♦ Copy to Output Directory: Copy if newer
♦ Now, “Show All Files” in Project menu can be switched off again
♦ Right click in the Content folder
Add ==> New Item…
♦ Add a HTML page with name index.html
♦ Replace the content with:
<!DOCTYPE html> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge" charset="utf-8" /> <title>AngularJS Test</title> <script src="angular.min.js"></script> <script src="ui-bootstrap-tpls-2.5.0.min.js"></script> <script src="ng-table.min.js"></script> <script src="main.js"></script> <link rel="stylesheet" href="bootstrap.min.css"> <link rel="stylesheet" href="ng-table.min.css"> <link rel="stylesheet" href="awesome-bootstrap-checkbox.css"> <!-- see https://angular-ui.github.io/bootstrap/ for an explanation --> <style> .nav, .pagination, .carousel, .panel-title a { cursor: pointer; } </style> </head> <body> <div class="container" ng-app="TestApp" ng-controller="TestCtrl"> <div class="page-header"> <h1 class="text-center">AngularJS Test</h1> </div> <table ng-table="tableParams" class="table table-bordered table-striped" show-filter="false"> <tr ng-repeat="person in $data" ng-form="rowForm"> <td title="'Name'" sortable="'Name'">{{person.Name}}</td> <td title="'Age'" sortable="'Age'">{{person.Age}}</td> </tr> </table> </div> </body> </html>
♦ Properties: Copy to Output Directory: Copy if newer
♦ Right click in the Content folder
♦ Add ==> New Item…
♦ Add a JavaScript file with name main.js
var app = angular.module('TestApp', ['ui.bootstrap', 'ngTable']); app.controller('TestCtrl', function ($scope, NgTableParams, personApi, $location) { $scope.persons = []; // configure ngTable and set above list as the data source $scope.tableParams = new NgTableParams({}, { dataset: $scope.persons }); // set the server for the REST API calls if (!$location.host()) { personApi.setServer('localhost'); } else { personApi.setServer($location.host()); } // call REST API and add the return data to the table personApi.getAllPersons().then(function (res) { var c = res.data.length; for (var i = 0; i < c; ++i) { var person = res.data[i]; $scope.persons.push(person); } // refresh table $scope.tableParams.reload(); }); }); // this is the interface for accessing our REST API app.factory("personApi", ['$http', function ($http) { var personApi = {}; personApi.getAllPersons = function () { return $http.get(personApi.urlBase + "persons"); }; personApi.setServer = function (server) { personApi.urlBase = 'http://' + server + '/personapiv1/'; }; return personApi; }]);
♦ Properties: Copy to Output Directory: Copy if newer
♦ Create a new class Person
public class Person { public string Name { get; set; } public int Age { get; set; } }
♦ Create a new class PersonsController
using System.Web.Http; using System.Net.Http; // your namespace ... [RoutePrefix("personapiv1")] public class PersonsController : ApiController { private List<Person> _persons; public PersonsController() { _persons = new List<Person>(); _persons.Add(new Person { Name = "Alice", Age = 9 }); _persons.Add(new Person { Name = "Bob", Age = 26 }); _persons.Add(new Person { Name = "Charly", Age = 17 }); _persons.Add(new Person { Name = "David", Age = 59 }); _persons.Add(new Person { Name = "Elizabeth", Age = 13 }); _persons.Add(new Person { Name = "Mary", Age = 67 }); _persons.Add(new Person { Name = "Edward", Age = 40 }); } [Route("persons")] [HttpGet] public IEnumerable<Person> GetAllPersons() { return _persons; } }
♦ Create a new class CustomHeaderHandler
using System.Threading.Tasks; using Microsoft.Owin; // your namespace ... public class CustomHeaderHandler : OwinMiddleware { public CustomHeaderHandler(OwinMiddleware next) : base(next) { } public override async Task Invoke(IOwinContext context) { context.Response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate"; context.Response.Headers["Pragma"] = "no-cache"; context.Response.Headers["Expires"] = "0"; await Next.Invoke(context); } }
♦ Create a new class Startup
using System; using Owin; using System.Web.Http; using Microsoft.Owin.FileSystems; using Microsoft.Owin.StaticFiles; using System.IO; // your namespace ... public class Startup { public void Configuration(IAppBuilder app) { var config = new HttpConfiguration(); config.MapHttpAttributeRoutes(); app.Use(typeof(CustomHeaderHandler)); app.UseWebApi(config); var root = AppDomain.CurrentDomain.BaseDirectory; var fileServerOptions = new FileServerOptions() { EnableDefaultFiles = true, EnableDirectoryBrowsing = false, FileSystem = new PhysicalFileSystem(Path.Combine(root, "Content")) }; app.UseFileServer(fileServerOptions); } }
♦ Edit the code of the xxxService class:
using Microsoft.Owin.Hosting;
♦ in the class:
private IDisposable _webApi; protected override void OnStart(string[] args) { string baseUri = "http://*"; _webApi = WebApp.Start<Startup>(baseUri); } protected override void OnStop() { _webApi.Dispose(); }
♦ For the installation, you need these files:
Content dir
xxxService.exe
xxxService.exe.config
Microsoft.Owin.dll
Microsoft.Owin.FileSystems.dll
Microsoft.Owin.Host.HttpListener.dll
Microsoft.Owin.Hosting.dll
Microsoft.Owin.StaticFiles.dll
Newtonsoft.Json.dll
Owin.dll
System.Net.Http.Formatting.dll
System.Web.Http.dll
System.Web.Http.Owin.dll
♦ In a commandline (run as administrator)
xxxService /i
The service is installed now and can be started.