Tuesday, January 23, 2018

Microsoft 70-487: Host and manage Web API

Exam Objectives

Host Web API in an ASP.NET app; self-host a Web API in your own process (a Windows service) including Open Web Interface for .NET (OWIN); host services in an Azure worker role; restrict message size; configure the host server for streaming


Quick Overview of Training Materials


Host in an ASP.NET app


The default Web API template in Visual Studio is set up for hosting as an ASP.NET application within IIS.  The entire fifth part of the exam objectives cover deployment, and there is probably also plenty of overlap with objective 3.9, Host and Manage Services.  Here is a quick start video on how to create a new application in IIS, just in case you forgot...



Self host with OWIN


Use OWIN to Self-Host ASP.NET Web API 2 gives a quick and dirty demo on how simple it is to create a self hosted OWIN server for Web API.  Create a new console application, and add the Microsoft.AspNet.WebApi.OwinSelfHost NuGet package.

Basically all that is needed is to define a class (they call it "Startup" but you could probably call it whatever since they are setting it as a type parameter later anyway) with a method called "Configuration" that takes an IAppBuilder as a parameter.  If you don't call it "Configuration", it will throw an EntryPointNotFoundException when you start it up.  Inside this Configuration method, you do all the same things you would normally do inside the Register config method in a Web API template application:


    class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            HttpConfiguration config = new HttpConfiguration();

            // Web API configuration and services
            config.Formatters.Clear();
            var json = new JsonMediaTypeFormatter();
            json.SerializerSettings.PreserveReferencesHandling =
                Newtonsoft.Json.PreserveReferencesHandling.All;
            json.SupportedMediaTypes.Clear();
            json.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
            config.Formatters.Add(json);
            //config.Formatters.Add(new XmlMediaTypeFormatter());
            config.Formatters.Add(new DogMediaFormatter());

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            config.Routes.MapHttpRoute(
                name: "ConstrainedApi",
                routeTemplate: "api/Constraints/{alpha}/{id}",
                defaults: new
                {
                    controller = "Constraints",
                    id = RouteParameter.Optional,
                    alpha = "alpha"
                },
                constraints: new
                {
                    alpha = @"[A-Za-z]+",
                    httpMethod = new HttpMethodConstraint(HttpMethod.Get)
                }
            );

            appBuilder.UseWebApi(config); 
        }

        private class CustomHttpBatchHandler : DefaultHttpBatchHandler
        {
            public CustomHttpBatchHandler(HttpServer httpServer) : base(httpServer)
            {
                this.ExecutionOrder = BatchExecutionOrder.NonSequential;
            }
        }
    }


I omitted the batch processing endpoint because batching is finicky in OWIN apparently. This blog post includes a work around: Mitigate Issue 260 in Batching Scenario, but I didn't feel like going to the trouble for a toy demo. Also notice that hte XmlMediaTypeFormatter is not added.  This was due to an issue that resurfaced from earlier demos, whereby getting the details on a single entity would throw an exception because the data contained circular references.  The Json formatter is configured to deal with these references, but I never fixed the XML formatter.

In the Program class of our little console app, we call WebApp.Start to start up the OWIN instance, passing in the base address we want to use for our service.  Because it's a demo, the inside of the using block creates an HttpClient and calls the service, in a realistic scenario it would probably be more analogous to the self hosted WCF service examples... my $0.02...



        static void Main(string[] args)
        {
            string baseAddress = "http://localhost:9000/";

            // Start OWIN host 
            using (WebApp.Start<Startup>(url: baseAddress))
            {
                // Create HttpCient and make a request to api/values 
                HttpClient client = new HttpClient();

                var response = client.GetAsync(baseAddress + "api/dogs").Result;

                Console.WriteLine(response);
                Console.WriteLine(response.Content.ReadAsStringAsync().Result);
                Console.ReadLine();
            }
        }


Voila, easy peasy:




Host in Azure worker role


Host ASP.NET Web API 2 in an Azure Worker Role outlines hosting an OWIN based server in an Azure Cloud Service worker role, and really once you have the OWIN hosting bit figured out, plopping it into Azure is pretty damn easy.  There are plenty of screenshots in the documentation to walk you through the initial setup, so I'll skip them here (they'd be exactly the same, and largely unnecessary if you've done it, like, once).

Just create an Azure Cloud Service project with a WorkerRole instance, create a Startup class as above (I literally just referenced the above project and reused it), and instead of spinning up OWIN in Main, you start up the WebApp instance in the WorkerRole's OnStart() method.  In the OnStop() method, dispose of the WebApp instance.  Couldn't get much easier:


       public override bool OnStart()
        {
            // Set the maximum number of concurrent connections
            ServicePointManager.DefaultConnectionLimit = 12;

            // For information on handling configuration changes
            // see the MSDN topic at https://go.microsoft.com/fwlink/?LinkId=166357.

            var endpoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["Endpoint1"];
            string baseUri = String.Format("{0}://{1}",
                endpoint.Protocol, endpoint.IPEndpoint);

            Trace.TraceInformation(String.Format("Starting OWIN at {0}", baseUri),
                "Information");

            _app = WebApp.Start<Startup>(new StartOptions(url: baseUri));
            return base.OnStart();
        }

        public override void OnStop()
        {
            Trace.TraceInformation("WorkerRole1 is stopping");

            if (_app != null)
            {
                _app.Dispose();
            }
            base.OnStop();

            Trace.TraceInformation("WorkerRole1 has stopped");
        }


Running the Azure Cloud Service project in debug mode will spin up the Azure Compute Emulator. It might remap your public port to avoid a conflict (mine changed it from 80 to 81).  Publishing to Azure is easily done by right clicking on the Azure Cloud Service and selecting "Publish", then walking through the steps.



Couple quick notes on debugging... if you forget to set the endpoint to use http, you'll get an exception (apparently tcp is not a supported protocol). And if you try to run it without Administrator permissions, you'll get an "Access is Denied" exception. 



Restrict message size


Finding a reliable source of information regarding this topic was problematic. How do I configure  max message size in  Web API gave me a couple ideas (which the Exam Ref shamelessly stole), but the HttpSelfHostConfiguration class they are using is basically considered "legacy" and not the recommended approach (MS says to use OWIN)... so there is that...



There is a forum thread on the ASP.NET site that addresses this; the answer is basically to use a custom message handler (recommends NuGet package) or to implement a custom OWIN middleware: Web API OWIN Host - Limit Request Size.

For Web API hosted in an ASP.NET app, one of the StackOverflow answers is to use web.config file to restrict incoming message size (though this does nothing for response size).  The change to the request filter is apparently necessary because ASP.NET, by default, filters requests larger than 30MB:


<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5.2" />
    <httpRuntime targetFramework="4.5.2" maxRequestLength="2147483647"/>
  </system.web>
  <system.webServer>
    <security>
      <requestFiltering>
        <requestLimits maxAllowedContentLength="4294967295"/>
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>


Configure host for streaming


Dealing with large files in ASP.NET Web API is the source used by the Exam Ref (shocker, more blatant plagiarism...), and Web API Request Streaming Support is a stack overflow question that basically gets answered by the same guy (Filip) who wrote the blog post.

The first step is to configure IIS to allow for large uploads, which we did in the section above.  He then explains how to create an implementation of IHostBufferPolicySelector that will force Web API to use streaming mode when the "uploading" controller is hit, and to buffer requests otherwise.  In the Stack Overflow version he points out some differences between .NET 4.5 and older versions regarding default buffering behavior in HttpClient.

His recommendations for self hosting do not apply to OWIN as they are using the HttpSelfHostConfiguration object to set the transfer mode.  I did find another blog post dealing with streamed data from a Web API app self hosted with OWIN, and it didn't seem to indicate that any special set up was required at all.  He just used techniques similar to what I discussed in the streaming section of my Implement a Web API post.



No comments:

Post a Comment