• 关于微软OWIN的一篇好文章


    ASP.NET - Getting Started with the Katana Project

    By Howard Dierking | October 2013

    When ASP.NET was first released back in 2002, times were different. The Internet was still in a state of relative infancy, with around 569 million users spending an average of 46 minutes a day on about 3 million Web sites. These same measurements taken just 10 years later reveal an Internet of approximately 2.27 billion users spending a daily average of 4 hours on 555 million Web sites (see bit.ly/MY7GzO).

    Clearly, that growth spurred corresponding changes in the needs of application developers in terms of the underlying frameworks, tools, and runtimes they use to build and run Web applications. Modern Web apps need to be able to evolve rapidly, taking advantage of many different components and frameworks—and they need a small footprint in order to run efficiently in the large-scale runtime of the cloud.

    Ensuring ASP.NET can meet these current and future needs is the core motivation behind the Katana project.

    What Is Katana?

    The seeds of the Katana project were actually sown outside of Microsoft with an open source project called the Open Web Inter­face for .NET (OWIN), a specification that defines the interactions between Web servers and application components (see owin.org). Because the goal of the specification is to stimulate a broad and vibrant ecosystem of Microsoft .NET Framework-based Web servers and application components, it reduces all interactions between servers and applications to a small set of types and a single function signature known as the application delegate, or AppFunc:

     
    using AppFunc = Func<IDictionary<string, object>, Task>;
    

    Every component in an OWIN-based application supplies an application delegate to a server. The components are then chained together into a pipeline into which an OWIN-based server pushes requests. In order to use resources efficiently, all components in the pipeline should be asynchronous, and this is reflected in the application delegate returning a Task object.

    All states, including application state, request state, server state and so forth, are held in the IDictionary<string, object> object specified on the application delegate. This data structure, known as the environment dictionary, is passed from component to component as a request progresses through the pipeline. While any key/value data may be inserted into the environment dictionary, the OWIN specification defines keys for some core elements of HTTP, as shown in Figure 1.

    Figure 1 Required Environment Dictionary Keys for an HTTP Request

    Key Name Value Description
    "owin.RequestBody" A Stream with the request body, if any. Stream.Null can be used as a placeholder if there’s no request body.
    "owin.RequestHeaders" An IDictionary<string, string[]> of request headers.
    "owin.RequestMethod" A string containing the HTTP request method of the request (such as GET and POST).
    "owin.RequestPath" A string containing the request path. The path must be relative to the “root” of the application delegate.
    "owin.RequestPathBase" A string containing the portion of the request path corresponding to the “root” of the application delegate.
    "owin.RequestProtocol" A string containing the protocol name and version (such as HTTP/1.0 or HTTP/1.1).
    "owin.RequestQueryString" A string containing the query string component of the HTTP request URI, without the leading “?” (such as foo=bar&baz=quux). The value may be an empty string.
    "owin.RequestScheme" A string containing the URI scheme used for the request (such as HTTP or HTTPS).

    Defining a base set of environment dictionary key-value pairs enables many different framework and component authors to interoperate in an OWIN pipeline, without forcing agreement on a specific .NET object model, such as those of HttpContextBase in ASP.NET MVC or HttpRequestMessage/HttpResponseMessage in ASP.NET Web API.

    These two elements—the application delegate and the environment dictionary—form the OWIN specification. The Katana project is the set of OWIN-based components and frameworks created and shipped by Microsoft.

    Katana components can be viewed through the architectural stack depicted in Figure 2.

    Katana Project Architecture
    Figure 2 Katana Project Architecture

    The stack consists of the following layers:

    • Host: The process that runs the application and can be anything from IIS or a standalone executable, to your own custom program. The host is responsible for startup, loading of other OWIN components and shutting down gracefully.
    • Server: Responsible for binding to a TCP port, constructing the environment dictionary and processing requests through an OWIN pipeline.
    • Middleware: The name given to all of the components that handle requests in an OWIN pipeline. It can range from a simple compression component to a complete framework such as ASP.NET Web API, though from the server’s perspective, it’s simply a component that exposes the application delegate.
    • Application: This is your code. Because Katana is not a replacement for ASP.NET but rather a new way to compose and host components, existing ASP.NET Web API and SignalR applications remain unchanged, as those frameworks can participate in an OWIN pipeline. In fact, for these kinds of applications, Katana components will be visible only in a small configuration class.

    Architecturally, Katana is factored such that each of these layers can be easily substituted, often without requiring a rebuild of the code. When processing an HTTP request, the layers work together in a manner similar to the data flow shown in Figure 3.

    Example of the Data Flow in Katana
    Figure 3 Example of the Data Flow in Katana

    Building a Modern Web Application with Katana

    Modern Web applications generally have four capabilities:

    1. Server-side markup generation
    2. Static file serving
    3. Web API for handling AJAX requests
    4. Real-time messaging

    Building an application with all of these capabilities requires a variety of different frameworks suitably specialized for the relevant capability. However, composing an application from such frameworks can be challenging, and currently requires hosting the different application parts on IIS, possibly isolating them from one another using applications and virtual directories.

    In contrast, Katana lets you compose a modern Web application from a wide range of different Web technologies and then host that application wherever you wish, exposed under a single HTTP endpoint. This provides several benefits:

    • Deployment is easy because it involves only a single application rather than one application per capability.
    • You can add additional capabilities, such as authentication, which can apply to all of the downstream components in the pipeline.
    • Different components, whether Microsoft or third-­party, can operate on the same request state via the environment dictionary.

    Now I’ll explore a sample application that has a domain you should be familiar with: bug tracking. The application will present a set of bugs in a variety of different states—backlog, working and done—and allow me to move the bug between states. And, because many different individuals could be managing bugs simultaneously, it will update all browsers in real time when the state of a bug changes. Here’s what I’ll use to build the application: Nancy (nancyfx.org) for server-side markup generation and static file serving; ASP.NET Web API (asp.net/web-api) for handling AJAX requests; and SignalR (signalr.net) for real-time messaging services.

    Additionally, while I’m not going to spend a great deal of time on markup and script for the browser client, I’ll use Knockout.js to separate the HTML markup from the Web API and SignalR data.

    The core principle to keep in mind is that I’m composing all of these different frameworks into a single OWIN pipeline, so as new capabilities become available I can add them to the application by simply inserting them into the pipeline.

    Getting Started

    One of the goals of Katana is to give you precise control over the capabilities added to your app (and, therefore, over what you spend in terms of performance for processing each request). With that in mind, I’ll begin by creating a new Empty ASP.NET Web app project in Visual Studio 2013 Preview, as shown inFigure 4.

    A New ASP.NET Web Application Project in Visual Studio 2013 Preview 
    Figure 4 A New ASP.NET Web Application Project in Visual Studio 2013 Preview

    Web project templates, even empty ones, provide a helpful feature in that, by default, they place compiled assemblies directly into the /bin folder rather than the /bin/debug folder (as is common in other project types). The default Katana host looks for assemblies in this /bin folder. You could create a Katana-based application as a class library, but you’d need to either modify your project properties to conform to this structure or supply your own custom application loader that could search for assemblies and types in a different folder structure.

    Next, I’ll build out the server-­side markup generation code using the Nancy Web framework.

    Nancy has a terse syntax, which makes it easy to quickly build HTTP-based sites and services. What’s more important for this exercise is that, like the ASP.NET Web API, it doesn’t have any dependencies on System.Web.dll and it’s built to run in an OWIN pipeline. Frameworks such as ASP.NET MVC have dependencies on System.Web.dll (at the time of this writing), which make them less ideal for non-IIS hosting scenarios.

    For the most part, when you add new functionality to the application, you’ll start by adding a NuGet package. (You can read more about NuGet at docs.nuget.org.) At the time of this writing, many of the packages being used here are prerelease versions, so make sure to allow prerelease packages to be displayed in the NuGet dialog.

    To add Nancy to the application, I could simply install the Nancy NuGet package. However, because I also want to run Nancy in an OWIN pipeline, I’m going to install the Nancy.Owin package (nuget.org/packages/nancy.owin). This will install the Nancy package as a dependency and provide additional helper methods for configuring Nancy in my OWIN pipeline.

    Next, I need to create a Nancy module (similar to a Model-­View-Controller, or MVC, controller) to handle requests, as well a view to display something to the browser. Here’s the code for the module (HomeModule.cs): 

     
    public class HomeModule : NancyModule
    {
      public HomeModule() {
        Get["/"] = _ => {
          var model = new { title = "We've Got Issues..." };
          return View["home", model];
        };
      }
    }
    

    As you can see, the module declares that requests directed to the application root (“/”) should be handled by the anonymous delegate defined in the associated lambda. That function creates a model containing the page title and instructs Nancy to render the “home” view, passing the model to the view. The view, shown in Figure 5, inserts the model’s title property into both the page title and h1 elements.

    Figure 5 Home.html
     
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
      <title>@Model.title</title>
    </head>
      <body>
        <header>
          <h1>@Model.title</h1>   
        </header>
        <section>
          <h2>Backlog</h2>
          <ul class="bugs" id="backlog">
            <li>a bug</li>
          </ul>
        </section>
        <section>
          <h2>Working</h2>
          <ul class="bugs" id="working">
            <li>a bug</li>
          </ul>
        </section>
        <section>
          <h2>Done</h2>
          <ul class="bugs" id="done">
            <li>a bug</li>
          </ul>
        </section>
      </body>
    </html>
    

    For more information about these listings, please refer to the Nancy documentation.

    Now that I’ve implemented the basic Nancy functionality, I need to establish an OWIN pipeline and configure the Nancy module to participate in that pipeline. For this, I need to first install the Katana host and server components, then write a small amount of plumbing code to set up the OWIN pipeline and insert Nancy into that pipeline.

    For the Katana host and server components, I’ll begin by using IIS Express and System.Web, as these are inherently understood by Visual Studio and will enable a smooth F5 experience while building the application. To incorporate the System.Web host into the project, I install the NuGet package Microsoft.Owin.Host.SystemWeb (bit.ly/19EZ2Rw).

    The default Katana components use several different conventions for loading and running OWIN applications, including the startup class. When a Katana host loads an OWIN application, it discovers and runs a startup class based on the following rules (in order of precedence):

    • If the web.config file contains an appSetting with key=“owin:AppStartup”, the loader uses the setting value. The value must be a valid .NET-type name.
    • If the assembly contains the attribute [assembly: OwinStartup(typeof(MyStartup))], the loader will use the type specified in the attribute value.
    • If neither of these conditions are true, the loader will scan the loaded assemblies looking for a type named Startup with a method that matches the signature void Configure(IAppBuilder app).

    For this example, I’ll allow the loader to scan assemblies for the class. However, if you have many different types and assemblies in your project, it would be wise to use either the appSetting or assembly attribute to prevent unnecessary scanning.

    I’ll create the startup class that will initialize my OWIN pipeline and add Nancy as a pipeline component. I create a new class called Startup and add a configuration method as follows:

     
    public class Startup
    {
      public void Configuration(IAppBuilder app)
      {
        app.UseNancy();
      }
    }
    

    UseNancy is an extension method made available by the Nancy.Owin NuGet package. While you can add middleware using the IAppBuilder’s more generic Use methods, many middleware libraries will provide these helpful extension methods that ease the configuration process.

    At this point, you can run the project in Visual Studio using F5 and see that though it’s not terribly exciting yet, you have a fully functional Web application. At this point, the OWIN pipeline consists of a single component, Nancy, as shown in Figure 6.

    A Functional Web Application with a Single Component
    Figure 6 A Functional Web Application with a Single Component

    Incorporating Data with ASP.NET Web API

    Currently, the HTML view consists of primary static markup. I’ll now give users some real bugs with which to work. For many modern Web apps, the task of delivering data to the browser client has shifted from the server-side markup generation framework (like the Nancy module) to a separate Web API service. The browser, then, loads the HTML page and immediately executes JavaScript, which fetches the data from the Web API and dynamically builds HTML markup in the page itself.

    I’ll start by constructing the Web API using the ASP.NET Web API framework. As usual, the first step is to install the Web API NuGet package. To ensure I can easily insert ASP.NET Web API into my OWIN pipeline, I’ll install the Microsoft.Asp­Net.WebApi.Owin package (bit.ly/1dnocmK). This package will install the rest of the ASP.NET Web API framework as dependencies. After installing the framework, I’ll create a simple API as shown in Figure 7.

    Figure 7 BugsController.cs
     
    public class BugsController : ApiController
    {
      IBugsRepository _bugsRepository = new BugsRepository();
      public IEnumerable<Bug> Get()
      {
        return _bugsRepository.GetBugs();
      }
      [HttpPost("api/bugs/backlog")]
      public Bug MoveToBacklog([FromBody] int id)
      {
        var bug = _bugsRepository.GetBugs().First(b=>b.id==id);
        bug.state = "backlog";
        return bug;
      }
      [HttpPost("api/bugs/working")]
      public Bug MoveToWorking([FromBody] int id)
      {
        var bug = _bugsRepository.GetBugs().First(b => b.id == id);
        bug.state = "working";
        return bug;
      }
      [HttpPost("api/bugs/done")]
      public Bug MoveToDone([FromBody] int id)
      {
        var bug = _bugsRepository.GetBugs().First(b => b.id == id);
        bug.state = "done";
        return bug;
      }
    }
    

    The API contains a method to return a set of bug objects from a repository, as well as some methods to move bugs between different states. Much more information about the ASP.NET Web API can be found at asp.net/web-api.

    Now that I have an ASP.NET Web API controller defined, I need to add it to my existing OWIN pipeline. To do this, I simply add the following lines to the Configuration method in my startup class:

     
    var config = new HttpConfiguration();
    config.MapHttpAttributeRoutes();
    config.Routes.MapHttpRoute("bugs", "api/{Controller}");
    app.UseWebApi(config);
    

    Just like Nancy, the ASP.NET Web API OWIN package provides the UseWebApi extension method, making it easy to incorporate the ASP.NET Web API into my existing OWIN pipeline. The OWIN pipeline now consists of two components, ASP.NET Web API and Nancy, as shown in Figure 8.

    The OWIN Pipeline with Two Components
    Figure 8 The OWIN Pipeline with Two Components

    As requests come into the pipeline, if they match one of the ASP.NET Web API routing rules, the ASP.NET Web API handles the request and generates a response. Otherwise, the request continues through the pipeline, where Nancy attempts to handle it. If no pipeline components can handle a particular request, the default Katana components will return an HTTP 404 response.

    Although I have a functioning ASP.NET Web API, it’s currently not being accessed by the home view. Therefore, I’ll add code to consume data from the Web API and generate a list of bugs in each of the different states: backlog, working and done. For this task, I’ll take advantage of Knockout.js, a JavaScript Model-View-ViewModel (MVVM) library. More information about Knockout can be found atknockoutjs.com.

    To enable dynamic, client-side generation of HTML markup using Knockout, the first thing I need to do is fetch all bugs from the ASP.NET Web API and create a viewModel that Knockout can bind to HTML elements. This is shown in Figure 9.

    Figure 9 Setting up the Bugs viewModel
     
    <script>
      $(function () {
        var viewModel;
        $.getJSON('/api/bugs', function(data) {
          var model = data;
          viewModel = {
            backlog: ko.observableArray(
              model.filter(function(element) { return element.state === 'backlog'; })),
            working: ko.observableArray(
              model.filter(function(element) { return element.state === 'working'; })),
            done: ko.observableArray(
              model.filter(function(element) { return element.state === 'done'; })),
            changeState: function (bug, newState) {
              var self = this;
              $.post('/api/bugs/' + newState, { '': bug.id }, function(data){
                self.moveBug(data);
              });
            },
            moveBug: function (bug) {
              // Remove the item from one of the existing lists
              ...
              // Add bug to correct list
              this[bug.state].push(bug);
            }
          };
          ko.applyBindings(viewModel);
        })
      })
    </script>
    

    Once the viewModel is created, Knockout can then dynamically generate and update HTML content by binding the viewModel to HTML elements decorated with Knockout-specific attributes. For example, the backlog list can be generated from the viewModel using the attributes shown in Figure 10.

    Figure 10 Attributes for Generating the Backlog List
     
    <section>
      <h2>Backlog</h2>
      <ul class="bugs" id="backlog" data-bind="foreach:backlog">
        <li>
          [<span data-bind="text: id"></span>] <span data-bind="text: title"></span>:
            <span data-bind="text: description"></span>
          <ul>
            <li><a href="#" data-bind="click: $root.changeState.bind($root, $data,
              'working')">Move to working</a></li>   
            <li><a href="#" data-bind="click: $root.changeState.bind($root, $data,
              'done')">Move to done</a></li>   
          </ul>
        </li>
      </ul>
    </section>
    

    Adding Real-Time Change Notifications with SignalR

    At this point, I have a fully functioning single-page Web application. Users can browse to the home view and move bugs between different bug states. Moreover, the underlying technologies for the current level of functionality, Nancy and ASP.NET Web API, are running together in the same OWIN pipeline.

    I’m going to go one step further, however, and allow different users to see, in real time, the updates made to bugs by other users. For this I’ll leverage the SignalR library, which provides both a client and server API for managing real-time message exchanges between the browser and a Web server. SignalR is also written to run in an OWIN pipeline, so adding it to my existing application will be trivial.

    I’ll use a SignalR feature called Hubs, and while the details of SignalR are beyond the scope of this article, a Hub enables clients and servers to call methods on one another. (For a great intro to SignalR, seebit.ly/14WIx1t.) In my application, when the ASP.NET Web API receives a request to change the state of a bug, it will update the bug and then broadcast the updated bug through the SignalR Hub to all browser clients currently connected to the application.

    I’ll start this process by creating a Hub on the server. Because I’m not taking advantage of any additional SignalR capabilities, my Hub will consist simply of the following empty class definition:

     
    [HubName("bugs")]
    public class BugHub : Hub
    {
    }
    

    In order to send broadcasts to the Hub from the ASP.NET Web API, I first need to get an instance to its runtime context. I can do this by adding the following BugsController constructor:

     
    public BugsController()
    {
      _hub = GlobalHost.ConnectionManager.GetHubContext<BugHub>();
    }
    

    From within one of the MoveToXX actions, I can then broadcast the updated bug to all of the connected browser clients:

     
    _hub.Clients.All.moved(bug);
    

    In the home view, after adding a couple of script references to the SignalR JavaScript libraries, I can connect to the bugsHub and start listening for “moved” messages with the following:

     
    $.connection.hub.logging = true;
    var bugsHub = $.connection.bugs;
    bugsHub.client.moved = function (item) {
      viewModel.moveBug(item);
    };
    $.connection.hub.start().done(function() {
      console.log('hub connection open');
    });
    

    Notice that when I receive a call from the server via the moved function, I call the viewModel moveBug method in the same way I did in the item’s click handler. The difference is that because this method is the result of a SignalR broadcast, all browser clients can update their viewModels at the same time. You can see this clearly by opening two browser windows, making changes in one, and then viewing the state change in the other.

    As I noted, adding SignalR to the OWIN pipeline is trivial. I simply add the following to the startup class Configuration method:

     
    app.MapSignalR();
    

    This creates a pipeline like the one in Figure 11.

    The OWIN Pipeline with Three Components
    Figure 11 The OWIN Pipeline with Three Components

    Moving to Self-Host

    I now have a functioning bug-management application that, while still missing some key features, can do a few interesting things. I’ve incrementally added capabilities to the application by using both Microsoft and third-party Katana middleware components. However, much of this was already possible today using ASP.NET HttpModules and HttpHandlers. So what have I really accomplished, other than providing a simpler, code-driven approach to compose the pipeline components?

    The key is to remember the high-level Katana architecture diagram in Figure 2. So far, I’ve just been working at the top two layers of the Katana stack. However, all of these layers can be easily replaced, including the server and host.

    To demonstrate, I’ll take my entire pipeline, lift it out of IIS and System.Web.dll, and sit it on top of a simple, lightweight HTTP server, which is being hosted by a Katana executable named OwinHost.exe. Self-hosting can prove useful in a variety of scenarios, ranging from setups where there’s no Web server installed on the development machine, to production situations where the application is being deployed in a shared-hosting environment that uses process isolation and doesn’t expose access to a Web server.

    I’ll start by installing the following additional NuGet packages:

    I’ll then rebuild the application. Note that rebuilding is not required in order to run the application on top of a new server and host. The only requirement is that those files exist in the /bin folder at run time, and rebuilding is a convenient way to have the files copied into /bin.

    After the packages have been installed and the files copied, I open a command prompt, navigate to the Web project’s root folder, and, as shown in Figure 12, call OwinHost.exe from within the packages folder:

     
    > ..packagesOwinHost.2.0.0	oolsOwinHost.exe
    

    Calling OwinHost.exe from Within the Packages Folder
    Figure 12 Calling OwinHost.exe from Within the Packages Folder

    By default, OwinHost.exe will launch, load the Microsoft.Ow­in.Host.HttpListener server, and begin listening on port 5000. I can then navigate to http://localhost:5000 to confirm the entire application is running.

    Furthermore, nearly all of the defaults can be overridden using command-line switches. For example, if you want to listen on a different port, supply -p 12345. If you want to use a completely different server, use -s your.custom.server.assembly. The power of the Katana design is its modularity. As innovations happen at any layer of the stack, they can be immediately integrated into running applications. And because the contract between all components of the stack is simply the application delegate, the pace of innovation can be much greater than what’s available now.

    Just Getting Started

    Katana 2.0 will be released with Visual Studio 2013. The new release has two main areas of focus:

    • Providing the core infrastructure components for self-hosting
    • Providing a rich set of middleware for authentication, including social providers such as Facebook, Google, Twitter, and Microsoft Account, as well as providers for Windows Azure Active Directory, cookies, and federation

    Once Katana 2.0 is released, work will begin immediately on the next set of Katana components. The details and priorities are still being determined, but you can influence that discussion by filing issues atkatanaproject.codeplex.com. Finally, all of the code for this article can be found at bit.ly/1alOF4m.


    Howard Dierking is a program manager on the Windows Azure Frameworks and Tools team where his focus is on ASP.NET, NuGet and Web APIs. Previously, Dierking served as the editor in chief of MSDN Magazine, and also ran the developer certification program for Microsoft Learning. He spent 10 years prior to Microsoft as a developer and application architect with a focus on distributed systems. 

    Thanks to the following Microsoft technical experts for reviewing this article: Brady Gaster and Scott Hanselman

  • 相关阅读:
    信息安全系统设计基础第八周期中复习总结
    layui下各种富文本的冲突情况
    TP3.2+find_set_in 以及 find_set_in和like的区别
    tp5+linux+apache php7.1.30环境下,上传图片报错:mkdir():permission denied
    一次基于老古董thinkPHP3.1的修改尝试
    微信网页开发 thinkphp5.0的try-catch和重定向
    CentOS 7.2下服务器配置(linux+apache+php+mysql)
    微信小程序踩坑(不定时更新)
    PHP 定时自动执行代码
    PHP TP5 文章评论+积分+签到
  • 原文地址:https://www.cnblogs.com/isingel/p/4789317.html
Copyright © 2020-2023  润新知