One of the project I have been working required a Windows service and I am familiar with VB.NET and services using VB.NET. I used to create a separate UI that communicates with the service and handle it but creating a proper user interface that looks good and functional was hard for me because I am much of the backend guy and with some knowledge of bootstrap. So learning some extras a Vue.js wasn’t that hard with available plugins. But first I need to host an application that holds the vue files and my API that communicates with the Vue.
So first step involving is preparations of what we need and I made the list:
- First we need to listen to a port
- Host Vue.js generated static files
- Add routes that handle API for different commands
And first step of listening to port is simple which relies on System.Web.Http.SelfHost
which need to be imported if not available. And then we add 3 things here:
- Message handlers that will handle static files or cases that we might require before processing any requests
HttpRoute
that handles route to our API- Config route to server with Auth if needed
- Listen and wait
1 2 3 4 5 6 7 8 9 10 |
Dim config = New System.Web.Http.SelfHost.HttpSelfHostConfiguration("http://localhost:8081/") config.MessageHandlers.Add(New CorsMiddleware()) config.MessageHandlers.Add(New MessageHandler()) Dim route = New System.Web.Http.Routing.HttpRoute("api/{controller}/{action}", New Routing.HttpRouteValueDictionary(New Dictionary(Of String, Object) From {{"controller", "Setting"}, {"action", "Index"}, {"_context", Me}})) config.Routes.Add("api", route) config.Filters.Add(New AuthMiddleware(Me)) server = New System.Web.Http.SelfHost.HttpSelfHostServer(config) server.OpenAsync().Wait() |
So we can read those basic line by line, we started a configuration to host a server at port 8081 on localhost and change to 0.0.0.0 for remote access. Next are the MessageHandlers that we add to the config which serves right before any request are called. I enabled CORS so I could test from any place temporarily. Here is the CORS for SelfHosted VB.NET code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Imports System.Net.Http Public Class CorsMiddleware Inherits DelegatingHandler Protected Overrides Function SendAsync(request As HttpRequestMessage, cancellationToken As Threading.CancellationToken) As Task(Of HttpResponseMessage) Return MyBase.SendAsync(request, cancellationToken).ContinueWith(Function(task As Task(Of HttpResponseMessage)) Dim resp As HttpResponseMessage = task.Result If request.Method.Method = "OPTIONS" Then resp.Content = New StringContent("OK") resp.StatusCode = Net.HttpStatusCode.OK End If resp.Headers.Add("Access-Control-Allow-Origin", "*") resp.Headers.Add("Access-Control-Allow-Methods", "DELETE, POST, GET, PUT, OPTIONS") resp.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With") Return resp End Function) End Function End Class |
Please note DelegatingHandler is inherited to handle the request.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
Private Class MessageHandler Inherits System.Net.Http.DelegatingHandler Protected Overrides Function SendAsync(request As Net.Http.HttpRequestMessage, cancellationToken As Threading.CancellationToken) As Task(Of Net.Http.HttpResponseMessage) 'My.Application.Log.WriteEntry(request.RequestUri.ToString()) If (Not request.RequestUri.AbsolutePath.StartsWith("/api/")) Then Dim path = request.RequestUri.AbsolutePath If (path = "/") Then path = "/index.html" End If Dim response = New System.Net.Http.HttpResponseMessage() Try Dim sr As New StreamReader(AppDomain.CurrentDomain.BaseDirectory & "/static" & path) response.Content = New StreamContent(sr.BaseStream) response.Content.Headers.ContentType = New Headers.MediaTypeHeaderValue(System.Web.MimeMapping.GetMimeMapping(path)) response.StatusCode = Net.HttpStatusCode.OK Catch ex As Exception Trace.WriteLine(ex.Message) response.Content = New StringContent("404") response.StatusCode = Net.HttpStatusCode.NotFound End Try Dim tsc = New TaskCompletionSource(Of Net.Http.HttpResponseMessage) tsc.SetResult(response) Return tsc.Task End If Return MyBase.SendAsync(request, cancellationToken) End Function End Class |
So what have we done is we want our API to be in /api/
route so we ignored the route and started looking for any file in the path relative to current executable direction and inside /static
path and handled the request with proper header needed. And defaulting the path with index.html
if no direct route path is defined while browsing. Now the final step is to listen to API which is defined on line 5. If no controller is defined we look for SettingController
, if no action is defined then it will look for a function named Index
and these values change the changed. For example, if you create a Controller called NetworkController
the route will looks as /api/network/
and further if any action defined or it will look for the default action set. Lets look onto a simple controller example.
1 2 3 4 5 6 7 8 9 10 11 |
Public Class SettingController Inherits ApiController <AllowAnonymous> <AcceptVerbs(("GET"))> Public Function Index() As Dictionary(Of String, Object) Dim dict As New Dictionary(Of String, Object) dict.Add("test",true) Return dict End Function End Class |
So if you browse this, it will respond a XML response on regular request, but if you send a request as content/json
, it will auto respond you to JSON format. There is not need for additional settings.
There you might see AllowAnonymous
, and you see I have added a filter on the code that will add filter if you want certain routes in the API to be accessed by Authenticated users only. The AuthMiddleware
Inherits a AuthorizeAttribute
that can overload the header and authentication can be done as per system.
Now that is all ready to host, we move to frontend part of coding with Vue.js that can be learnt from different online sources. Once we initialize a project, we set the build path to the static
folder inside the output directory of the service and then voila, we are ready to serve Vue.js directly from the service itself.
Since being a standalone, it doesn’t require to install anything else but just the output directly only. The service being self hosted will act and look like ASP.NET server but we can heavily customize to most levels. I have yet to learn on working with WebSocket and Server Side Events on this type of service.