public void Configuration(IAppBuilder app) { // Configure WebApi var config = new HttpConfiguration(); config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}", new { id = RouteParameter.Optional }); app.UseWebApi(config); // Configure SignalR app.MapHubs(); }It looks, as if there are some features get activated with app.UseXXX() (or app.MapHubs(), respectively). But this is not completely true. When we look into the implementation of these "feature activating methods", they finally call
public IAppBuilder Use(object middleware, params object[] args) { this._middleware.Add(AppBuilder.ToMiddlewareFactory(middleware, args)); return (IAppBuilder) this; }This method adds the features to a list. During the request processing, every feature in this list will be checked, if it can handle the request. Therefore the order of the "feature activating methods" can be important. Not in the example above, since WebApi and SignalR do not compete for the same requests.
Microsoft.Owin.Diagnostics
The package Microsoft.Owin.Diagnostics contains one useful feature catching and displaying exceptions. For sure this shouldn't happen, but when it would be nice to know about. But as always you should consider to use it only during development.To switch it on, simply add the following line at the beginning of Startup.Configuration():
app.UseShowExceptions();
Another feature in Microsoft.Owin.Diagnostics is
app.UseTestPage();This displays the message Welcome to Katana to the client. You should place it at the very end of Configuration.Startup(). Otherwise your other features wouldn't never reached.
But anyway, this feature is useful only in hello world status. Later I would prefer to get HTTP 404 instead of this message.
Microsoft.Owin.StaticFiles
Like most of the other OWIN packages, also Microsoft.Owin.StaticFiles is in prerelease status. But this package is special, you even cannot find it in NuGet. To install it, you need to enter the following command in the Package Manager Console:Install-Package Microsoft.Owin.StaticFiles -Version 0.20-alpha-20220-88 -PreBut probably the package is hidden since it doesn't work really. It has problems when you request more than one file in parallel. The solution is to use one of the nightly Katana builds (e.g. 0.24.0-pre-20624-416). Probably this is even more alpha than the hidden version. But is works better. Obviously there was some improvement between version 0.20 and 0.24.
You can get the nightly builds from a separate feed: http://www.myget.org/f/Katana.
After adding the package, just add one line to Startup.Configuration():
app.UseStaticFiles("StaticFiles");The parameter specifies, in which directory the static files will be searched. Since I named it StaticFiles, you have to add a folder with this name to your project. And for every file you add to this folder you have to set in its properties that it will be copied to the output directory:
When you start the project, you can fire up the browser and enter http://localhost:8080/test.htm (without specifying the StaticFiles folder), and you get simply the page back.
Logging OWIN requests
Sometimes it would be interesting to see the incoming requests in a trace. This can be achieved with a custom feature. The constructor is quite simple. It just stores the reference to the next feature:private readonly Func<IDictionary<string, object>, Task> _next; public Logger(Func<IDictionary<string, object>, Task> next) { if (next == null) throw new ArgumentNullException("next"); _next = next; }The implementation isn't really complicated, too:
public Task Invoke(IDictionary<string, object> environment) { string method = GetValueFromEnvironment(environment, OwinConstants.RequestMethod); string path = GetValueFromEnvironment(environment, OwinConstants.RequestPath); Console.WriteLine("Entry\t{0}\t{1}", method, path); Stopwatch stopWatch = Stopwatch.StartNew(); return _next(environment).ContinueWith(t => { Console.WriteLine("Exit\t{0}\t{1}\t{2}\t{3}\t{4}", method, path, stopWatch.ElapsedMilliseconds, GetValueFromEnvironment(environment, OwinConstants.ResponseStatusCode), GetValueFromEnvironment(environment, OwinConstants.ResponseReasonPhrase)); return t; }); }First, it prints some data of the current request. The more interesting part is, that it then calls the succeeding features (return _next(environment)). And when the succeeding features were evaluated, it finally (ContinueWith) prints some response data. I added here also method and path, since otherwise it were difficult to find the corresponding entries. Maybe it would be even better to use some kind of unique id for this purpose. But in my projects, method and path are enough.
GetValueFromEnvironment is only a little helper, since in some cases the environment dictionary does not contains all values:
private static string GetValueFromEnvironment(IDictionary<string, object> environment, string key) { object value; environment.TryGetValue(key, out value); return Convert.ToString(value, CultureInfo.InvariantCulture); }Since the Logger traces the beginning and the end of the processing, it should be activated quite at the beginning of Startup.Configuration():
app.Use(typeof(Logger));
Logging the Request Body
With POST requests, it can be very handy to log also the request body. For this, you have only to extend the Invoke method a little bit:string requestBody; Stream stream = (Stream)environment[OwinConstants.RequestBody]; using (StreamReader sr = new StreamReader(stream)) { requestBody = sr.ReadToEnd(); } environment[OwinConstants.RequestBody] = new MemoryStream(Encoding.UTF8.GetBytes(requestBody));The access to the request body is provided by a Stream. The only caveat with this stream is that it is not seekable. That means you can read it only once. And maybe the simple logging will not be enough for your requirements. Sometimes you will also process it afterwards...
Fortunately, this is no big issue: just replace the old stream with a new MemoryStream. For sure, this is not good idea with big request bodies. In such a case you will need a more sophisticated solution. But normally, it should be good enough. Moreover, you can use it only during development. In production you can disable it by configuration, par example.
Logging SignalR
With the Logger from above, you get a nice logging of the several SignalR requests (when it uses LongPolling instead of WebSockets):10:51:18,181 11 Entry GET /signalr/negotiate 10:51:18,263 8 Exit GET /signalr/negotiate 82 10:51:18,271 11 Entry GET /signalr/ping 10:51:18,275 8 Exit GET /signalr/ping 4 10:51:18,540 11 Entry GET /signalr/connect 10:53:08,806 14 Exit GET /signalr/connect 110260 10:53:08,826 11 Entry GET /signalr/poll 10:54:58,960 15 Exit GET /signalr/poll 110128 10:54:58,969 11 Entry GET /signalr/poll 10:56:49,136 12 Exit GET /signalr/poll 110161The used Logger prints also timestamp and thread id (in the first 2 columns). It is easily to see, how SignalR waits for 110 seconds for an answer from the server (a notification). When it doesn't get one, it simply sends the next request.
You can also see that all requests (at least in this example) are handled by the same thread (11). But the response is created by other threads (8, 14, 15 and 12).
Summary
It is quite easy to add additional features to an OWIN host. And it is also not too hard to implement own features. Drawbacks of the whole stuff are (hopefully only at the moment):- the beta status of some packages
- the lack of documentation
You can find the source code at GitHub: https://github.com/Ritzlgrmft/OwinConfiguration.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.