Remainder of initial HttpClient features plus E2E tests

This commit is contained in:
Steve Sanderson 2018-02-23 14:43:57 +00:00
parent ea3a18af25
commit 8590f6e7a5
10 changed files with 463 additions and 48 deletions

View File

@ -168,19 +168,25 @@ export class BrowserRenderer {
const browserRendererId = this.browserRendererId;
const eventHandlerId = renderTreeFrame.attributeEventHandlerId(attributeFrame);
if (attributeName === 'value') {
if (this.tryApplyValueProperty(toDomElement, renderTreeFrame.attributeValue(attributeFrame))) {
return; // If this DOM element type has special 'value' handling, don't also write it as an attribute
}
}
// TODO: Instead of applying separate event listeners to each DOM element, use event delegation
// and remove all the _blazor*Listener hacks
switch (attributeName) {
case 'onclick': {
toDomElement.removeEventListener('click', toDomElement['_blazorClickListener']);
const listener = () => raiseEvent(browserRendererId, componentId, eventHandlerId, 'mouse', { Type: 'click' });
const listener = evt => raiseEvent(evt, browserRendererId, componentId, eventHandlerId, 'mouse', { Type: 'click' });
toDomElement['_blazorClickListener'] = listener;
toDomElement.addEventListener('click', listener);
break;
}
case 'onchange': {
toDomElement.removeEventListener('change', toDomElement['_blazorChangeListener']);
const listener = evt => raiseEvent(browserRendererId, componentId, eventHandlerId, 'change', { Type: 'change', Value: evt.target.value });
const listener = evt => raiseEvent(evt, browserRendererId, componentId, eventHandlerId, 'change', { Type: 'change', Value: evt.target.value });
toDomElement['_blazorChangeListener'] = listener;
toDomElement.addEventListener('change', listener);
break;
@ -192,7 +198,7 @@ export class BrowserRenderer {
// just to establish that we can pass parameters when raising events.
// We use C#-style PascalCase on the eventInfo to simplify deserialization, but this could
// change if we introduced a richer JSON library on the .NET side.
raiseEvent(browserRendererId, componentId, eventHandlerId, 'keyboard', { Type: evt.type, Key: (evt as any).key });
raiseEvent(evt, browserRendererId, componentId, eventHandlerId, 'keyboard', { Type: evt.type, Key: (evt as any).key });
};
toDomElement['_blazorKeypressListener'] = listener;
toDomElement.addEventListener('keypress', listener);
@ -208,6 +214,18 @@ export class BrowserRenderer {
}
}
tryApplyValueProperty(element: Element, value: string | null) {
// Certain elements have built-in behaviour for their 'value' property
switch (element.tagName) {
case 'INPUT':
case 'SELECT': // Note: this doen't handle <select> correctly: https://github.com/aspnet/Blazor/issues/157
(element as any).value = value;
return true;
default:
return false;
}
}
insertFrameRange(componentId: number, parent: Element, childIndex: number, frames: System_Array<RenderTreeFramePointer>, startIndex: number, endIndexExcl: number): number {
const origChildIndex = childIndex;
for (let index = startIndex; index < endIndexExcl; index++) {
@ -243,7 +261,9 @@ function removeAttributeFromDOM(parent: Element, childIndex: number, attributeNa
element.removeAttribute(attributeName);
}
function raiseEvent(browserRendererId: number, componentId: number, eventHandlerId: number, eventInfoType: EventInfoType, eventInfo: any) {
function raiseEvent(event: Event, browserRendererId: number, componentId: number, eventHandlerId: number, eventInfoType: EventInfoType, eventInfo: any) {
event.preventDefault();
if (!raiseEventMethod) {
raiseEventMethod = platform.findMethod(
'Microsoft.AspNetCore.Blazor.Browser', 'Microsoft.AspNetCore.Blazor.Browser.Rendering', 'BrowserRendererEventDispatcher', 'DispatchEvent'

View File

@ -1,27 +1,61 @@
import { registerFunction } from '../Interop/RegisteredFunction';
import { platform } from '../Environment';
import { MethodHandle } from '../Platform/Platform';
import { MethodHandle, System_String } from '../Platform/Platform';
const httpClientAssembly = 'Microsoft.AspNetCore.Blazor.Browser';
const httpClientNamespace = `${httpClientAssembly}.Services.Temporary`;
const httpClientTypeName = 'HttpClient';
const httpClientFullTypeName = `${httpClientNamespace}.${httpClientTypeName}`;
let receiveResponseMethod: MethodHandle;
registerFunction(`${httpClientFullTypeName}.Send`, (id: number, requestUri: string) => {
sendAsync(id, requestUri);
registerFunction(`${httpClientFullTypeName}.Send`, (id: number, method: string, requestUri: string, body: string | null, headersJson: string | null) => {
sendAsync(id, method, requestUri, body, headersJson);
});
async function sendAsync(id: number, requestUri: string) {
async function sendAsync(id: number, method: string, requestUri: string, body: string | null, headersJson: string | null) {
let response: Response;
let responseText: string;
try {
const response = await fetch(requestUri);
const responseText = await response.text();
dispatchResponse(id, response.status, responseText, null);
response = await fetch(requestUri, {
method: method,
body: body,
headers: headersJson ? (JSON.parse(headersJson) as string[][]) : undefined
});
responseText = await response.text();
} catch (ex) {
dispatchResponse(id, 0, null, ex.toString());
dispatchErrorResponse(id, ex.toString());
return;
}
dispatchSuccessResponse(id, response, responseText);
}
function dispatchResponse(id: number, statusCode: number, responseText: string | null, errorInfo: string | null) {
function dispatchSuccessResponse(id: number, response: Response, responseText: string) {
const responseDescriptor: ResponseDescriptor = {
StatusCode: response.status,
Headers: []
};
response.headers.forEach((value, name) => {
responseDescriptor.Headers.push([name, value]);
});
dispatchResponse(
id,
platform.toDotNetString(JSON.stringify(responseDescriptor)),
platform.toDotNetString(responseText), // TODO: Consider how to handle non-string responses
/* errorMessage */ null
);
}
function dispatchErrorResponse(id: number, errorMessage: string) {
dispatchResponse(
id,
/* responseDescriptor */ null,
/* responseText */ null,
platform.toDotNetString(errorMessage)
);
}
function dispatchResponse(id: number, responseDescriptor: System_String | null, responseText: System_String | null, errorMessage: System_String | null) {
if (!receiveResponseMethod) {
receiveResponseMethod = platform.findMethod(
httpClientAssembly,
@ -30,11 +64,20 @@ function dispatchResponse(id: number, statusCode: number, responseText: string |
'ReceiveResponse'
);
}
platform.callMethod(receiveResponseMethod, null, [
platform.toDotNetString(id.toString()),
platform.toDotNetString(statusCode.toString()),
responseText === null ? null : platform.toDotNetString(responseText),
errorInfo === null ? null : platform.toDotNetString(errorInfo.toString())
responseDescriptor,
responseText,
errorMessage,
]);
}
// Keep this in sync with the .NET equivalent in HttpClient.cs
interface ResponseDescriptor {
// We don't have BodyText in here because if we did, then in the JSON-response case (which
// is the most common case), we'd be double-encoding it, since the entire ResponseDescriptor
// also gets JSON encoded. It would work but is twice the amount of string processing.
StatusCode: number;
Headers: string[][];
}

View File

@ -2,9 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Blazor.Browser.Interop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Blazor.Browser.Services.Temporary
@ -50,7 +53,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services.Temporary
return await response.Content.ReadAsStringAsync();
}
// <summary>
/// <summary>
/// Sends a GET request to the specified URI and returns the response as
/// an instance of <see cref="HttpResponseMessage"/> in an asynchronous
/// operation.
@ -58,6 +61,30 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services.Temporary
/// <param name="requestUri">The URI the request is sent to.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public Task<HttpResponseMessage> GetAsync(string requestUri)
=> SendAsync(new HttpRequestMessage(HttpMethod.Get, requestUri));
/// <summary>
/// Sends a POST request to the specified URI and returns the response as
/// an instance of <see cref="HttpResponseMessage"/> in an asynchronous
/// operation.
/// </summary>
/// <param name="requestUri">The URI the request is sent to.</param>
/// <param name="content">The content for the request.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content)
=> SendAsync(new HttpRequestMessage(HttpMethod.Post, requestUri)
{
Content = content
});
/// <summary>
/// Sends an HTTP request to the specified URI and returns the response as
/// an instance of <see cref="HttpResponseMessage"/> in an asynchronous
/// operation.
/// </summary>
/// <param name="request">The request to be sent.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
var tcs = new TaskCompletionSource<HttpResponseMessage>();
int id;
@ -67,12 +94,36 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services.Temporary
_pendingRequests.Add(id, tcs);
}
RegisteredFunction.Invoke<object>($"{typeof(HttpClient).FullName}.Send", id, requestUri);
RegisteredFunction.Invoke<object>(
$"{typeof(HttpClient).FullName}.Send",
id,
request.Method.Method,
request.RequestUri.ToString(),
request.Content == null ? null : await GetContentAsString(request.Content),
SerializeHeadersAsJson(request));
return tcs.Task;
return await tcs.Task;
}
private static void ReceiveResponse(string id, string statusCode, string responseText, string errorText)
private string SerializeHeadersAsJson(HttpRequestMessage request)
=> Json.Serialize(
(from header in request.Headers.Concat(request.Content?.Headers ?? Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>())
from headerValue in header.Value // There can be more than one value for each name
select new[] { header.Key, headerValue }).ToList()
);
private static async Task<string> GetContentAsString(HttpContent content)
=> content is StringContent stringContent
? await stringContent.ReadAsStringAsync()
: throw new InvalidOperationException($"Currently, {typeof(HttpClient).FullName} " +
$"only supports contents of type {nameof(StringContent)}, but you supplied " +
$"{content.GetType().FullName}.");
private static void ReceiveResponse(
string id,
string responseDescriptorJson,
string responseBodyText,
string errorText)
{
TaskCompletionSource<HttpResponseMessage> tcs;
var idVal = int.Parse(id);
@ -82,17 +133,42 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services.Temporary
_pendingRequests.Remove(idVal);
}
if (errorText == null)
if (errorText != null)
{
tcs.SetResult(new HttpResponseMessage
{
StatusCode = (HttpStatusCode)int.Parse(statusCode),
Content = new StringContent(responseText)
});
tcs.SetException(new HttpRequestException(errorText));
}
else
{
tcs.SetException(new HttpRequestException(errorText));
var responseDescriptor = Json.Deserialize<ResponseDescriptor>(responseDescriptorJson);
var responseContent = responseBodyText == null ? null : new StringContent(responseBodyText);
var responseMessage = responseDescriptor.ToResponseMessage(responseContent);
tcs.SetResult(responseMessage);
}
}
// Keep in sync with TypeScript class in Http.ts
private class ResponseDescriptor
{
#pragma warning disable 0649
public int StatusCode { get; set; }
public string[][] Headers { get; set; }
#pragma warning restore 0649
public HttpResponseMessage ToResponseMessage(HttpContent content)
{
var result = new HttpResponseMessage((HttpStatusCode)StatusCode);
result.Content = content;
var headers = result.Headers;
var contentHeaders = result.Content?.Headers;
foreach (var pair in Headers)
{
if (!headers.TryAddWithoutValidation(pair[0], pair[1]))
{
contentHeaders?.TryAddWithoutValidation(pair[0], pair[1]);
}
}
return result;
}
}
}

View File

@ -10,6 +10,7 @@
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="Selenium.Support" Version="3.8.0" />
<PackageReference Include="Selenium.WebDriver" Version="3.8.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />

View File

@ -1,8 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using BasicTestApp.HttpClientTest;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
using System.Net.Http;
using System.Threading.Tasks;
@ -13,6 +16,10 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public class HttpClientTest : BasicTestAppTestBase, IClassFixture<AspNetSiteServerFixture>
{
readonly ServerFixture _apiServerFixture;
readonly IWebElement _appElement;
IWebElement _responseStatus;
IWebElement _responseBody;
IWebElement _responseHeaders;
public HttpClientTest(
BrowserFixture browserFixture,
@ -23,7 +30,8 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
apiServerFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
_apiServerFixture = apiServerFixture;
//Navigate(ServerPathBase, noReload: true);
Navigate(ServerPathBase, noReload: true);
_appElement = MountTestComponent<HttpRequestsComponent>();
}
[Fact]
@ -36,5 +44,91 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
var responseText = await httpClient.GetStringAsync("/api/greeting/sayhello");
Assert.Equal("Hello", responseText);
}
[Fact]
public void CanPerformGetRequest()
{
IssueRequest("GET", "/api/person");
Assert.Equal("OK", _responseStatus.Text);
Assert.Equal("[\"value1\",\"value2\"]", _responseBody.Text);
}
[Fact]
public void CanPerformPostRequestWithBody()
{
var testMessage = $"The value is {Guid.NewGuid()}";
IssueRequest("POST", "/api/person", testMessage);
Assert.Equal("OK", _responseStatus.Text);
Assert.Equal($"You posted: {testMessage}", _responseBody.Text);
}
[Fact]
public void CanReadResponseHeaders()
{
IssueRequest("GET", "/api/person");
Assert.Equal("OK", _responseStatus.Text);
// Note that we only see header names case insensitively. The 'fetch' API
// can use whatever casing rules it wants, because the HTTP spec says the
// names are case-insensitive. In practice, Chrome lowercases them all.
// Ideally we should make the test case-insensitive for header name, but
// case-sensitive for header value.
Assert.Contains("mycustomheader: My custom value", _responseHeaders.Text);
}
[Fact]
public void CanSendRequestHeaders()
{
AddRequestHeader("TestHeader", "Value from test");
AddRequestHeader("another-header", "Another value");
IssueRequest("DELETE", "/api/person");
Assert.Equal("OK", _responseStatus.Text);
Assert.Contains("TestHeader: Value from test", _responseBody.Text);
Assert.Contains("another-header: Another value", _responseBody.Text);
}
[Fact]
public void CanSendAndReceiveJson()
{
AddRequestHeader("Content-Type", "application/json");
IssueRequest("PUT", "/api/person", "{\"Name\": \"Bert\", \"Id\": 123}");
Assert.Equal("OK", _responseStatus.Text);
Assert.Contains("Content-Type: application/json", _responseHeaders.Text);
Assert.Equal("{\"id\":123,\"name\":\"Bert\"}", _responseBody.Text);
}
private void IssueRequest(string requestMethod, string relativeUri, string requestBody = null)
{
var targetUri = new Uri(_apiServerFixture.RootUri, relativeUri);
SetValue("request-uri", targetUri.AbsoluteUri);
SetValue("request-body", requestBody ?? string.Empty);
new SelectElement(Browser.FindElement(By.Id("request-method")))
.SelectByText(requestMethod);
_appElement.FindElement(By.Id("send-request")).Click();
new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(
driver => driver.FindElement(By.Id("response-status")) != null);
_responseStatus = _appElement.FindElement(By.Id("response-status"));
_responseBody = _appElement.FindElement(By.Id("response-body"));
_responseHeaders = _appElement.FindElement(By.Id("response-headers"));
}
private void AddRequestHeader(string name, string value)
{
var addHeaderButton = _appElement.FindElement(By.Id("add-header"));
addHeaderButton.Click();
var newHeaderEntry = _appElement.FindElement(By.CssSelector(".header-entry:last-of-type"));
var textBoxes = newHeaderEntry.FindElements(By.TagName("input"));
textBoxes[0].SendKeys(name);
textBoxes[1].SendKeys(value);
}
private void SetValue(string elementId, string value)
{
var element = Browser.FindElement(By.Id(elementId));
element.Clear();
element.SendKeys(value);
}
}
}

View File

@ -0,0 +1,123 @@
@using System.Net
@using System.Net.Http
@inject Microsoft.AspNetCore.Blazor.Browser.Services.Temporary.HttpClient Http
<h1>HTTP request tester</h1>
<p>
<div>URI:</div>
<input id="request-uri" value=@uri @onchange(value => { uri = (string)value; }) size="60"/>
</p>
<p>
<div>Method:</div>
<select id="request-method" value=@method @onchange(value => { method = (string)value; })>
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
</select>
</p>
<p>
<div>Request body:</div>
<textarea id="request-body" value=@requestBody @onchange(value => { requestBody = (string)value; })></textarea>
</p>
<p>
<div>Request headers:</div>
@foreach (var header in requestHeaders)
{
<div class="header-entry">
Name: <input value=@header.Name @onchange(value => { header.Name = (string)value; }) />
Value: <input value=@header.Value @onchange(value => { header.Value = (string)value; }) />
[<a href="#" @onclick(() => RemoveHeader(header))>remove</a>]
</div>
}
<button id="add-header" @onclick(AddHeader)>Add</button>
</p>
<button id="send-request" @onclick(DoRequest)>Request</button>
@if (responseStatusCode.HasValue)
{
<h2>Response</h2>
<p><div>Status:</div><span id="response-status">@responseStatusCode</span></p>
<p><div>Body:</div><textarea id="response-body" readonly>@responseBody</textarea></p>
<p><div>Headers:</div><textarea id="response-headers" readonly>@responseHeaders</textarea></p>
}
<style type="text/css">
textarea {
width: 100%;
height: 60px;
}
</style>
@functions {
string uri = "http://api.icndb.com/jokes/random";
string method = "GET";
string requestBody = "";
List<RequestHeader> requestHeaders = new List<RequestHeader>();
HttpStatusCode? responseStatusCode;
string responseBody;
string responseHeaders;
async void DoRequest()
{
responseStatusCode = null;
try
{
var requestMessage = new HttpRequestMessage()
{
Method = new HttpMethod(method),
RequestUri = new Uri(uri),
Content = string.IsNullOrEmpty(requestBody)
? null
: new StringContent(requestBody)
};
foreach (var header in requestHeaders)
{
if (!requestMessage.Headers.TryAddWithoutValidation(header.Name, header.Value))
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Name, header.Value);
}
}
var response = await Http.SendAsync(requestMessage);
responseStatusCode = response.StatusCode;
responseBody = await response.Content.ReadAsStringAsync();
var allHeaders = response.Headers.Concat(response.Content?.Headers
?? Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>());
responseHeaders = string.Join(
Environment.NewLine,
allHeaders.Select(pair => $"{pair.Key}: {pair.Value.First()}").ToArray());
}
catch (Exception ex)
{
if (ex is AggregateException)
{
ex = ex.InnerException;
}
responseStatusCode = HttpStatusCode.SeeOther;
responseBody = ex.Message + Environment.NewLine + ex.StackTrace;
}
StateHasChanged();
}
void AddHeader()
=> requestHeaders.Add(new RequestHeader());
void RemoveHeader(RequestHeader header)
=> requestHeaders.Remove(header);
class RequestHeader
{
public string Name { get; set; }
public string Value { get; set; }
}
}

View File

@ -1,4 +1,4 @@
@using Microsoft.AspNetCore.Blazor.Routing
<c:Router AppAssembly=@(typeof(BasicTestApp.Program).Assembly)
PagesNamespace=@nameof(BasicTestApp)
DefaultComponentName=@("Default") />
PagesNamespace=@nameof(BasicTestApp)
DefaultComponentName=@("Default") />

View File

@ -6,16 +6,44 @@
<base href="/subdir/" />
</head>
<body>
<div id="test-selector" style="display: none">
Select test:
<select onchange="mountTestComponent(event.target.value)">
<option value="">Choose...</option>
<option value="BasicTestApp.AddRemoveChildComponents">Add/remove child components</option>
<option value="BasicTestApp.CounterComponent">Counter</option>
<option value="BasicTestApp.CounterComponentUsingChild">Counter using child component</option>
<option value="BasicTestApp.CounterComponentWrapper">Counter wrapped in parent</option>
<option value="BasicTestApp.KeyPressEventComponent">Key press event</option>
<option value="BasicTestApp.ParentChildComponent">Parent component with child</option>
<option value="BasicTestApp.PropertiesChangedHandlerParent">Parent component that changes parameters on child</option>
<option value="BasicTestApp.RedTextComponent">Red text</option>
<option value="BasicTestApp.RenderFragmentToggler">Render fragment renderer</option>
<option value="BasicTestApp.TextOnlyComponent">Plain text</option>
<option value="BasicTestApp.HierarchicalImportsTest.Subdir.ComponentUsingImports">Imports statement</option>
<option value="BasicTestApp.HttpClientTest.HttpRequestsComponent">HttpClient tester</option>
<!--<option value="BasicTestApp.RouterTest.Default">Router</option> Excluded because it requires additional setup to work correctly when loaded manually -->
</select>
&nbsp;
<span id="source-info"></span>
<hr />
</div>
<app>Loading...</app>
<script type="blazor-boot"></script>
<script>
// The client-side .NET code calls this when it is ready to be called from test code
// The Xunit test code polls until it sees the flag is set
Blazor.registerFunction('testReady', function () { window.isTestReady = true; });
Blazor.registerFunction('testReady', function () {
window.isTestReady = true;
document.getElementsByTagName('APP')[0].textContent = '';
document.getElementById('test-selector').style.display = 'block';
});
// The Xunit test code calls this when setting up tests for specific components
function mountTestComponent(typeName) {
document.getElementById('source-info').innerHTML = '<code><tt>' + typeName.replace(/\./g, '/') + '.cshtml</code></strong>';
var method = Blazor.platform.findMethod('BasicTestApp', 'BasicTestApp', 'Program', 'MountTestComponent');
Blazor.platform.callMethod(method, null, [Blazor.platform.toDotNetString(typeName)]);
}

View File

@ -1,41 +1,60 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace TestServer.Controllers
{
[EnableCors("AllowAll")]
[Route("api/[controller]")]
public class PersonController : Controller
{
// GET api/values
// GET api/person
[HttpGet]
public IEnumerable<string> Get()
{
HttpContext.Response.Headers.Add("MyCustomHeader", "My custom value");
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/values
// POST api/person
[HttpPost]
public void Post([FromBody]string value)
public async Task<string> Post()
{
using (var reader = new StreamReader(Request.Body))
{
var plainTextBodyContent = await reader.ReadToEndAsync();
return $"You posted: {plainTextBodyContent}";
}
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
// PUT api/person
[HttpPut]
public Person Put([FromBody, Required] Person person)
{
return person;
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
// DELETE api/person
[HttpDelete]
public string Delete()
{
var result = new StringBuilder();
foreach (var header in Request.Headers)
{
result.AppendLine($"{header.Key}: {string.Join(",", header.Value.ToArray())}");
}
return "REQUEST HEADERS:\n" + result.ToString();
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
}
}

View File

@ -18,6 +18,17 @@ namespace TestServer
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddCors(options =>
{
options.AddPolicy("AllowAll", builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.WithExposedHeaders("MyCustomHeader");
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.