警告
This page documents version 1.0.0-rc1 and has not yet been updated for version 1.0.0
OWIN¶
By Steve Smith
ASP.NET Core supports OWIN, the Open Web Interface for .NET, which allows web applications to be decoupled from web servers. In addition, OWIN defines a standard way for middleware to be used in a pipeline to handle individual requests and associated responses. ASP.NET Core applications and middleware can interoperate with OWIN-based applications, servers, and middleware.
Sections:
Running OWIN middleware in the ASP.NET pipeline¶
ASP.NET Core’s OWIN support is deployed as part of the Microsoft.AspNetCore.Owin
package. You can import OWIN support into your project by adding this package as a dependency in your project.json file, as shown here:
"dependencies": {
"Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
"Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
"Microsoft.AspNet.Owin": "1.0.0-rc1-final"
},
OWIN middleware conform to the OWIN specification, which defines a Properties IDictionary<string, object>
interface that must be used, and also requires certain keys be set (such as owin.ResponseBody
). We can construct a very simple example of middleware that follows the OWIN specification to display “Hello World”, as shown here:
public Task OwinHello(IDictionary<string, object> environment)
{
string responseText = "Hello World via OWIN";
byte[] responseBytes = Encoding.UTF8.GetBytes(responseText);
// OWIN Environment Keys: http://owin.org/spec/owin-1.0.0.html
var responseStream = (Stream)environment["owin.ResponseBody"];
var responseHeaders = (IDictionary<string, string[]>)environment["owin.ResponseHeaders"];
responseHeaders["Content-Length"] = new string[] { responseBytes.Length.ToString(CultureInfo.InvariantCulture) };
responseHeaders["Content-Type"] = new string[] { "text/plain" };
return responseStream.WriteAsync(responseBytes, 0, responseBytes.Length);
}
In the above example, notice that the method returns a Task
and accepts an IDictionary<string, object>
as required by OWIN. Within the method, this parameter is used to retrieve the owin.ResponseBody
and owin.ResponseHeaders
objects from the environment dictionary. Once the headers are set appropriately for the content being returned, a task representing the asynchronous write to the response stream is returned.
Adding OWIN middleware to the ASP.NET pipeline is most easily done using the UseOwin
extension method. Given the OwinHello
method shown above, adding it to the pipeline is a simple matter:
public void Configure(IApplicationBuilder app)
{
app.UseOwin(pipeline =>
{
pipeline(next => OwinHello);
});
}
You can of course configure other actions to take place within the OWIN pipeline. Remember that response headers should only be modified prior to the first write to the response stream, so configure your pipeline accordingly.
注解
Multiple calls to UseOwin
is discouraged for performance reasons. OWIN components will operate best if grouped together.
app.UseOwin(pipeline =>
{
pipeline(next =>
{
// do something before
return OwinHello;
// do something after
});
});
注解
The OWIN support in ASP.NET Core is an evolution of the work that was done for the Katana project. Katana’s IAppBuilder
component has been replaced by IApplicationBuilder
, but if you have existing Katana-based middleware, you can use it within your ASP.NET Core application through the use of a bridge, as shown in the Owin.IAppBuilderBridge example on GitHub.
Using ASP.NET Hosting on an OWIN-based server¶
OWIN-based servers can host ASP.NET applications, since ASP.NET conforms to the OWIN specification. One such server is Nowin, a .NET OWIN web server. In the sample for this article, I’ve included a very simple project that references Nowin and uses it to create a simple server capable of self-hosting ASP.NET Core.
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNet.Hosting.Server;
using Microsoft.AspNet.Owin;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNet.Http.Features;
using Nowin;
namespace NowinSample
{
public class NowinServerFactory : IServerFactory
{
private Func<IFeatureCollection, Task> _callback;
private Task HandleRequest(IDictionary<string, object> env)
{
return _callback(new FeatureCollection(new OwinFeatureCollection(env)));
}
public IFeatureCollection Initialize(IConfiguration configuration)
{
var builder = ServerBuilder.New()
.SetAddress(IPAddress.Any)
.SetPort(5000)
.SetOwinApp(HandleRequest);
var serverFeatures = new FeatureCollection();
serverFeatures.Set<INowinServerInformation>(new NowinServerInformation(builder));
return serverFeatures;
}
public IDisposable Start(IFeatureCollection serverFeatures,
Func<IFeatureCollection, Task> application)
{
var information = serverFeatures.Get<INowinServerInformation>();
_callback = application;
INowinServer server = information.Builder.Build();
server.Start();
return server;
}
private class NowinServerInformation : INowinServerInformation
{
public NowinServerInformation(ServerBuilder builder)
{
Builder = builder;
}
public ServerBuilder Builder { get; private set; }
public string Name
{
get
{
return "Nowin";
}
}
}
}
}
|
IServerFactory
is an interface that requires an Initialize
and a Start
method. Initialize must return an instance of IFeatureCollection
, which we populate with a INowinServerInformation
that includes the server’s name (the specific implementation may provide additional functionality). In this example, the NowinServerInformation
class is defined as a private class within the factory, and is returned by Initialize
as required.
Initialize
is responsible for configuring the server, which in this case is done through a series of fluent API calls that hard code the server to listen for requests (to any IP address) on port 5000. Note that the final line of the fluent configuration of the builder
variable specifies that requests will be handled by the private method HandleRequest
.
Start
is called after Initialize
and accepts the the IFeatureCollection
created by Initialize
, and a callback of type Func<IFeatureCollection, Task>
. This callback is assigned to a local field and is ultimately called on each request from within the private HandleRequest
method (which was wired up in Initialize
).
With this in place, all that’s required to run an ASP.NET application using this custom server is the following command in project.json:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | {
"version": "1.0.0-*",
"compilationOptions": {
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
"Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
"Microsoft.AspNet.Owin": "1.0.0-rc1-final",
"Nowin": "0.22.0"
},
"commands": {
"web": "Microsoft.AspNet.Hosting --server NowinSample"
},
|
When run, this command will search for a package called “NowinSample” that contains an implementation of IServerFactory
. If it finds one, it will initialize and start the server as detailed above. Learn more about the built-in ASP.NET Servers.
Run ASP.NET Core on an OWIN-based server and use its WebSockets support¶
Another example of how OWIN-based servers’ features can be leveraged by ASP.NET Core is access to features like WebSockets. The .NET OWIN web server used in the previous example has support for Web Sockets built in, which can be leveraged by an ASP.NET Core application. The example below shows a simple web application that supports Web Sockets and simply echos back anything sent to the server via WebSockets.
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
if (context.WebSockets.IsWebSocketRequest)
{
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
await EchoWebSocket(webSocket);
}
else
{
await next();
}
});
app.Run(context =>
{
return context.Response.WriteAsync("Hello World");
});
}
private async Task EchoWebSocket(WebSocket webSocket)
{
byte[] buffer = new byte[1024];
WebSocketReceiveResult received = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);
while (!webSocket.CloseStatus.HasValue)
{
// Echo anything we receive
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, received.Count),
received.MessageType, received.EndOfMessage, CancellationToken.None);
received = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer),
CancellationToken.None);
}
await webSocket.CloseAsync(webSocket.CloseStatus.Value,
webSocket.CloseStatusDescription, CancellationToken.None);
}
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
}
|
This sample is configured using the same NowinServerFactory
as the previous one - the only difference is in how the application is configured in its Configure
method. A simple test using a simple websocket client demonstrates that the application works as expected:
OWIN keys¶
OWIN depends heavily on an IDictionary<string,object>
used to communicate information throughout an HTTP Request/Response exchange. ASP.NET Core implements all of the required and optional keys outlined in the OWIN specification, as well as some of its own. Note that any keys not required in the OWIN specification are optional and may only be used in some scenarios. When working with OWIN keys, it’s a good idea to review the list of OWIN Key Guidelines and Common Keys
Request Data (OWIN v1.0.0)¶
Key | Value (type) | Description |
---|---|---|
owin.RequestScheme | String |
|
owin.RequestMethod | String |
|
owin.RequestPathBase | String |
|
owin.RequestPath | String |
|
owin.RequestQueryString | String |
|
owin.RequestProtocol | String |
|
owin.RequestHeaders | IDictionary<string,string[]> |
|
owin.RequestBody | Stream |
Request Data (OWIN v1.1.0)¶
Key | Value (type) | Description |
---|---|---|
owin.RequestId | String |
Optional |
Response Data (OWIN v1.0.0)¶
Key | Value (type) | Description |
---|---|---|
owin.ResponseStatusCode | int |
Optional |
owin.ResponseReasonPhrase | String |
Optional |
owin.ResponseHeaders | IDictionary<string,string[]> |
|
owin.ResponseBody | Stream |
Other Data (OWIN v1.0.0)¶
Key | Value (type) | Description |
---|---|---|
owin.CallCancelled | CancellationToken |
|
owin.Version | String |
Common Keys¶
Key | Value (type) | Description |
---|---|---|
ssl.ClientCertificate | X509Certificate |
|
ssl.LoadClientCertAsync | Func<Task> |
|
server.RemoteIpAddress | String |
|
server.RemotePort | String |
|
server.LocalIpAddress | String |
|
server.LocalPort | String |
|
server.IsLocal | bool |
|
server.OnSendingHeaders | Action<Action<object>,object> |
SendFiles v0.3.0¶
Key | Value (type) | Description |
---|---|---|
sendfile.SendAsync | See delegate signature | Per Request |
Opaque v0.3.0¶
Key | Value (type) | Description |
---|---|---|
opaque.Version | String |
|
opaque.Upgrade | OpaqueUpgrade |
See delegate signature |
opaque.Stream | Stream |
|
opaque.CallCancelled | CancellationToken |
WebSocket v0.3.0¶
Key | Value (type) | Description |
---|---|---|
websocket.Version | String |
|
websocket.Accept | WebSocketAccept |
See delegate signature. |
websocket.AcceptAlt | Non-spec | |
websocket.SubProtocol | String |
See RFC6455 Section 4.2.2 Step 5.5 |
websocket.SendAsync | WebSocketSendAsync |
See delegate signature. |
websocket.ReceiveAsync | WebSocketReceiveAsync |
See delegate signature. |
websocket.CloseAsync | WebSocketCloseAsync |
See delegate signature. |
websocket.CallCancelled | CancellationToken |
|
websocket.ClientCloseStatus | int |
Optional |
websocket.ClientCloseDescription | String |
Optional |