I am trying to post Datadog-logs from my C# application. I managed to send my logs with the desired structure using Postman, but I just can't figure out how to achieve the same goal from the code.
What I tried:
Using DogStatsD - but I don't want to install an agent, I'd much rather use the Datadog REST API to just post my logs.
Using Serilog.Sinks.Datadog.Logs - Which seems to be pretty easy to use, but I can't figure out how this works, and whether it is possible to change the log structure or not. By default, there are MessageTemplate and Properties fields in the resulting json. I'd like to be able to send my own structure in one message, rather then use the MessageTemplate. Is that possible?
The desired log to be seen in Datadog UI Logs Section:
{
hostname: myHost
myStuff {
item1: item_val1
item2: item_val2
}
otherStuff: oh wow this is cool
service: MyService
}
Here's what I sent using Postman to achieve this result:
URL: https://http-intake.logs.datadoghq.com/v1/input
Headers:
DD-API-KEY: my_api_key
Content-Type: application/json
Body:
{
"ddsource": "mySource",
"ddtags": "myTag: myVal, myValWithoutTag",
"hostname": "myHost",
"message": {
"myStuff":
{
"item1": "item_val1",
"item2": "item_val2"
},
"otherStuff": "oh wow this is cool"
},
"service": "MyService"
}
Is it possible to achieve the same (or even similar) result using datalog serilog sinks? If not, how can I achieve this result in C#?
Here is what I tried from the code:
var config = new DatadogConfiguration(url: "intake.logs.datadoghq.com", port: 443, useSSL: true, useTCP: true);
using (var log = new LoggerConfiguration().WriteTo.DatadogLogs(
"<myApiKey>",
source: "mySource",
service: "myService",
host: "myHost",
tags: new string[] {"myTag:myVal", "myValWithoutTag"},
configuration: config
).
CreateLogger())
{
var messageTemplate = "{message}";
var message = new
{
myStuff = new
{
item1 = "item_val1",
item2 = "item_val2"
}
};
log.Information(messageTemplate, message);
}
With the undesired result in Datadog UI Logs section:
{
host: myHost
level: Information
MessageTemplate: {message}
Properties: {
message: { myStuff = { item1 = item_val1, item2 = item_val2 } }
}
service: myService
Timestamp: 2021-05-17T00:13:14.2614896+03:00
}
The tags part did work, and also the host and service parts are the same. I don't mind the level and Timestamp additions,
but I'd love to change the body to behave like in the Postman example (just the message as JSON).
So my questions are:
Is it possible to control the message body format using Datadog Serilog sinks?
Is there any good alternative that I didn't try? (except for writing my own client, which is what I'm leaning towards)
Can anyone explain to me how it works? I can't figure out the concept of the sink. Can anyone explain how it works? And why is there no actual REST HTTP client for this task?
Thanks!
You really no need use Agent for sending and customizing logs to Datadog. Its pretty cool for providing traces and other metrics. Unfortunately, MessageTemplate could`t be changed in Datadog logging configuration. Only put MessageTemplate like message in Serilog:
var message = "Service started";
logger.Information(message);
Properties you can add 2 ways.
Using Serilog Enrich:
var config = new DatadogConfiguration(url: "intake.logs.datadoghq.com", port: 443, useSSL: true, useTCP: true);
using (var log = new LoggerConfiguration()
.Enrich.WithProperty(item1, item_val1)
.WriteTo.DatadogLogs(
"<myApiKey>",
source: "mySource",
service: "myService",
host: "myHost",
tags: new string[] {"myTag:myVal", "myValWithoutTag"},
configuration: config
).
CreateLogger())
Or push property in LogContext by middleware:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Serilog.Context;
namespace MyProject.Middlewares
{
public class LogPropertyMiddleware
{
private readonly RequestDelegate _next;
public LogUserInfoMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
LogContext.PushProperty("item1", "item_val1");
await _next.Invoke(context);
}
}
}
Add middleware to startup:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.....
.....
app.UseMiddleware<LogPropertyMiddleware>();
.....
}
Related
We have set up application insights with our ASP.NET Core 6 application which uses application insights for logging. We use a RestSharp based HTTP client for executing HTTP requests which works fine.
When trying to debug calls made through the RestSharp library, I am not seeing any response bodies being logged, only the statuscode, while the requests are being (kind of) logged:
{
"name": "AppDependencies",
"time": "2023-02-02T06:05:04.6268266Z",
"tags": {
"ai.application.ver": "1.0.0.0",
"ai.cloud.roleInstance": "MY_NICE_PC",
"ai.user.id": "ltK4V",
"ai.operation.id": "11bf52695a8d8ea19f1cb7573f2b195b",
"ai.operation.parentId": "324234234234",
"ai.operation.name": "POST to/somewhere [v]",
"ai.location.ip": "::1",
"ai.internal.sdkVersion": "rdddsc:2.21.0-429",
"ai.internal.nodeName": "MY_NICE_PC"
},
"data": {
"baseType": "RemoteDependencyData",
"baseData": {
"ver": 2,
"name": "POST /none/of-your/business",
"id": "bfa554335eefae0b",
"data": "https://none.of.your.business/my-nice-api-0/my=1&nice&=2&querystring=3",
"duration": "00:00:04.8666247",
"resultCode": "422",
"success": false,
"type": "Http",
"target": "none.of.your.business",
"properties": {
"DeveloperMode": "true",
"AspNetCoreEnvironment": "localdev",
"_MS.ProcessedByMetricExtractors": "(Name:'Dependencies', Ver:'1.1')"
}
}
}
}
We are using ASP.NET Core 6.0 with the Microsoft.ApplicationInsights.AspNetCore version 2.2.21 and the following DI setup:
services.AddApplicationInsightsTelemetry(configure =>
{
configure.ConnectionString = "MySecretConnectionString";
configure.EnableAdaptiveSampling = false;
});
--- Edit:
I also made an implementation of the ITelemetryInitializer to capture all telemetry for all instances of DependencyTelemetry:
public class DependencyTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
// SKIP for now
if (telemetry is DependencyTelemetry dependencyTelemetry)
{
}
}
}
This showed me that for every dependency call only the request is being captured, but the response is not.
That is by design, the dependency call basically logs the outgoing call, as that is what application insights has access to. It logs the duration, and also the response code (422 in your example.) I am also not sure what kind of information you would expect to see from the response.
If you want to get access to the response you can do so in a TelemetryInitializer:
public class CustomInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
if (telemetry is DependencyTelemetry dt
&& dt.Type.Equals("http", System.StringComparison.InvariantCultureIgnoreCase)
&& dt.TryGetHttpResponseOperationDetail(out var response))
{
...
}
}
}
You can then access the headers etc. Technically you can access the response content but since it is a stream by default it can only be read. So reading the full response content can't be done here withouth breaking the application afaik.
Also, even if you could, you should be carefull regarding logging the request and response playload. The size might be large, and Application Insights is not designed for that kind of log data. You might also hit the limits to the lenght of the custom properties and the logged payload will be cut off.
I came up with the following, since it is needed to buffer the response, so that it can be read again, I have added the LoadIntoBufferAsync. Also this seems be available as Async only.
public void Initialize(ITelemetry telemetry)
{
if (telemetry is DependencyTelemetry dependencyTelemetry &&
dependencyTelemetry.Type.Equals(DEPENDENCYTELEMETRY_TYPE_HTTP,
StringComparison.InvariantCultureIgnoreCase)
&& dependencyTelemetry.TryGetHttpResponseOperationDetail(out var response))
{
var task = Task.Run(async () => await response.Content.LoadIntoBufferAsync());
task. Wait();
var stream = response.Content.ReadAsStream();
using var reader = new StreamReader(
stream,
Encoding.UTF8,
detectEncodingFromByteOrderMarks: false,
bufferSize: 512, leaveOpen: true);
var responseBody = reader.ReadToEnd();
dependencyTelemetry.Properties.Add("responseBody", responseBody);
}
}
This only seems to be working with very small stream sizes (response sizes) less than 1K. I
I am building a simple restaurant management system in WPF. I have my backend in Laravel. I needed to setup a web socket to get real-time notifications on WPF app when a customer places an order from mobile app. I configured the web socket in Laravel using beyondcode/laravel-websockets. For ease, I tested the web socket on client side using laravel-echo with Vue. Everything works well there but I couldn't find any solution to replicate laravel-echo in C#.
Here is the code I am using in Vue.js with laravel-echo:
import Echo from "laravel-echo";
import Pusher from "pusher-js";
window.Pusher = Pusher;
const token = "1|CSaob3KZhU5UHiocBjPgzpazbceUKTLRLJO0ZIV0"
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'laravel_rdb',
wsHost: '127.0.0.1',
authEndpoint: 'http://localhost/may-app/public/broadcasting/auth',
encrypted: false,
forceTLS: false,
wsPort: 6001,
wssPort: 6001,
disableStats: true,
enabledTransports: ['ws', 'wss'],
auth : {
headers : {
Authorization: "Bearer " + token,
Accept: "application/json",
}
},
})
window.Echo.private('customer-order')
.listen('OrderPlaced', (e) => {
console.log(e)
})
I found SocketIOClient is used to implement web socket functionality in .NET. I tried to use a solution I found here but it didn't work for me. Also, I didn't find any way to set up my authentication URL in this package. I read socket.io documentation for anything related to authentication but I couldn't find any.
How do I implement equivalent functionality in C# .NET as in laravel-echo?
There is probably no client like laravel-echo for .NET. However, you will be able to connect to your sockets using pusher client: pusher/pusher-websocket-dotnet and this is probably the highest level of compatibility you can reach. But you will need to parse your messages and subscribe to the channels by yourself, there will be no sweet wrapping like in laravel-echo =(
I was able to implement a solution using the package mentioned by PunyFlash in the answers. The NuGet package is available here and here is the GitHub repo.
My solution might be useful for someone in the future so, my equivalent code for the laravel-echo code above, in .NET is:
internal class OrderSocket
{
public static async void Connect()
{
try
{
//Setting authentication
var authorizer = new CustomAuthorizer("http://localhost/may-app/public/broadcasting/auth")
{
AuthenticationHeader = new System.Net.Http.Headers.AuthenticationHeaderValue("Authorization", "Bearer " + "1|CSaob3KZhU5UHiocBjPgzpazbceUKTLRLJO0ZIV0"),
};
//Creating pusher object with authentication
Pusher pusher = new Pusher("laravel_rdb", new PusherOptions
{
Authorizer = authorizer,
Host = "127.0.0.1:6001",
});
//Connecting to web socket
await pusher.ConnectAsync().ConfigureAwait(false);
//Subscribing to channel
Channel channel = await pusher.SubscribeAsync("private-customer-order").ConfigureAwait(false);
if (channel.IsSubscribed)
{
//Binding to an event
channel.Bind("App\\Events\\OrderPlaced", (PusherEvent eventResponse) =>
{
// Deserialize json if server returns json values
Debug.WriteLine(eventResponse.Data);
});
}
}
catch (Exception)
{
Debug.WriteLine("An exception occurred.");
}
}
}
//HttpAuthorizer child class to set default headers
internal class CustomAuthorizer : HttpAuthorizer
{
public CustomAuthorizer(string authEndpoint) : base(authEndpoint) { }
public override void PreAuthorize(HttpClient httpClient)
{
base.PreAuthorize(httpClient);
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
}
}
I am creating a package lib for all the errors in a Webapi service. This library will be used for providing custom responses for BadRequest, BadArgument, ApiVersionsing etc.. related errors. I need help in customizing Apiversion related errors for - ApiVersionUnspecified, UnsupportedApiVersion, InvalidApiVersion, AmbiguousApiVersion. I have follow this article to include api-versioning for my project - https://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspx
I have checked the github wiki for the above package and found that "Depending on the desired behavior, you can extend the DefaultErrorResponseProvider or you can implement your own IErrorResponseProvider from stratch.
To wire up an alternate error response behavior, replace the default provider with your own:"
options => options.ErrorResponses = new MyErrorResponseProvider();
However; I am not quite getting how can I customize the default error responses in MyErrorResponseProvider class. Can somebody please provide me with any example so I can get started with this?
Thanks in advance!
Found the way of implementing above as -
class MyErrorResponseProvider : DefaultErrorResponseProvider
{
// note: in Web API the response type is HttpResponseMessage
public override IActionResult CreateResponse( ErrorResponseContext context )
{
switch ( context.ErrorCode )
{
case "UnsupportedApiVersion":
context = new ErrorResponseContext(
context.Request,
context.StatusCode,
context.ErrorCode,
"My custom error message.",
context.MessageDetail );
break;
}
return base.CreateResponse( context );
}
}
Thanks to github issue # - https://github.com/Microsoft/aspnet-api-versioning/issues/233
The answer customizes only the error message returned by the ASP.NET API Versioning.
To customize the whole response you can implement it by returning ObjectResult.
Startup.cs
// Add API Versioning to the service container to your project
services.AddApiVersioning(config =>
{
// Advertise the API versions supported for the particular endpoint
config.ReportApiVersions = true;
config.ErrorResponses = new ApiVersioningErrorResponseProvider();//Send standard error response when API version error.
});
ApiVersioningErrorResponseProvider.cs
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
public class ApiVersioningErrorResponseProvider : DefaultErrorResponseProvider
{
public override IActionResult CreateResponse(ErrorResponseContext context)
{
//You can initialize your own class here. Below is just a sample.
var errorResponse = new
{
ResponseCode = 101,
ResponseMessages = "Something went wrong while selecting the api version",
HelpLink = "https://github.com/microsoft/aspnet-api-versioning/wiki/Error-Response-Provider"
};
var response = new ObjectResult(errorResponse);
response.StatusCode = (int)HttpStatusCode.BadRequest;
return response;
}
}
Which produces below output:
{
"ResponseCode": 101,
"ResponseMessages": "Something went wrong while selecting the api version",
"HelpLink": "https://github.com/microsoft/aspnet-api-versioning/wiki/Error-Response-Provider"
}
I'm developing an ASP.Net Core web application where I need to create a kind of "authentication proxy" to another (external) web service.
What I mean by authentication proxy is that I will receive requests through a specific path of my web app and will have to check the headers of those requests for an authentication token that I'll have issued earlier, and then redirect all the requests with the same request string / content to an external web API which my app will authenticate with through HTTP Basic auth.
Here's the whole process in pseudo-code
Client requests a token by making a POST to a unique URL that I sent him earlier
My app sends him a unique token in response to this POST
Client makes a GET request to a specific URL of my app, say /extapi and adds the auth-token in the HTTP header
My app gets the request, checks that the auth-token is present and valid
My app does the same request to the external web API and authenticates the request using BASIC authentication
My app receives the result from the request and sends it back to the client
Here's what I have for now. It seems to be working fine, but I'm wondering if it's really the way this should be done or if there isn't a more elegant or better solution to this? Could that solution create issues in the long run for scaling the application?
[HttpGet]
public async Task GetStatement()
{
//TODO check for token presence and reject if issue
var queryString = Request.QueryString;
var response = await _httpClient.GetAsync(queryString.Value);
var content = await response.Content.ReadAsStringAsync();
Response.StatusCode = (int)response.StatusCode;
Response.ContentType = response.Content.Headers.ContentType.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;
await Response.WriteAsync(content);
}
[HttpPost]
public async Task PostStatement()
{
using (var streamContent = new StreamContent(Request.Body))
{
//TODO check for token presence and reject if issue
var response = await _httpClient.PostAsync(string.Empty, streamContent);
var content = await response.Content.ReadAsStringAsync();
Response.StatusCode = (int)response.StatusCode;
Response.ContentType = response.Content.Headers.ContentType?.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;
await Response.WriteAsync(content);
}
}
_httpClient being a HttpClient class instantiated somewhere else and being a singleton and with a BaseAddressof http://someexternalapp.com/api/
Also, is there a simpler approach for the token creation / token check than doing it manually?
If anyone is interested, I took the Microsoft.AspNetCore.Proxy code and made it a little better with middleware.
Check it out here: https://github.com/twitchax/AspNetCore.Proxy. NuGet here: https://www.nuget.org/packages/AspNetCore.Proxy/. Microsoft archived the other one mentioned in this post, and I plan on responding to any issues on this project.
Basically, it makes reverse proxying another web server a lot easier by allowing you to use attributes on methods that take a route with args and compute the proxied address.
[ProxyRoute("api/searchgoogle/{query}")]
public static Task<string> SearchGoogleProxy(string query)
{
// Get the proxied address.
return Task.FromResult($"https://www.google.com/search?q={query}");
}
I ended up implementing a proxy middleware inspired by a project in Asp.Net's GitHub.
It basically implements a middleware that reads the request received, creates a copy from it and sends it back to a configured service, reads the response from the service and sends it back to the caller.
This post talks about writing a simple HTTP proxy logic in C# or ASP.NET Core. And allowing your project to proxy the request to any other URL. It is not about deploying a proxy server for your ASP.NET Core project.
Add the following code anywhere of your project.
public static HttpRequestMessage CreateProxyHttpRequest(this HttpContext context, Uri uri)
{
var request = context.Request;
var requestMessage = new HttpRequestMessage();
var requestMethod = request.Method;
if (!HttpMethods.IsGet(requestMethod) &&
!HttpMethods.IsHead(requestMethod) &&
!HttpMethods.IsDelete(requestMethod) &&
!HttpMethods.IsTrace(requestMethod))
{
var streamContent = new StreamContent(request.Body);
requestMessage.Content = streamContent;
}
// Copy the request headers
foreach (var header in request.Headers)
{
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
requestMessage.Headers.Host = uri.Authority;
requestMessage.RequestUri = uri;
requestMessage.Method = new HttpMethod(request.Method);
return requestMessage;
}
This method covert user sends HttpContext.Request to a reusable HttpRequestMessage. So you can send this message to the target server.
After your target server response, you need to copy the responded HttpResponseMessage to the HttpContext.Response so the user's browser just gets it.
public static async Task CopyProxyHttpResponse(this HttpContext context, HttpResponseMessage responseMessage)
{
if (responseMessage == null)
{
throw new ArgumentNullException(nameof(responseMessage));
}
var response = context.Response;
response.StatusCode = (int)responseMessage.StatusCode;
foreach (var header in responseMessage.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
foreach (var header in responseMessage.Content.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
// SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
response.Headers.Remove("transfer-encoding");
using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
{
await responseStream.CopyToAsync(response.Body, _streamCopyBufferSize, context.RequestAborted);
}
}
And now the preparation is complete. Back to our controller:
private readonly HttpClient _client;
public YourController()
{
_client = new HttpClient(new HttpClientHandler()
{
AllowAutoRedirect = false
});
}
public async Task<IActionResult> Rewrite()
{
var request = HttpContext.CreateProxyHttpRequest(new Uri("https://www.google.com"));
var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, HttpContext.RequestAborted);
await HttpContext.CopyProxyHttpResponse(response);
return new EmptyResult();
}
And try to access it. It will be proxied to google.com
A nice reverse proxy middleware implementation can also be found here: https://auth0.com/blog/building-a-reverse-proxy-in-dot-net-core/
Note that I replaced this line here
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
with
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToString());
Original headers (e.g. like an authorization header with a bearer token) would not be added without my modification in my case.
I had luck using twitchax's AspNetCore.Proxy NuGet package, but could not get it to work using the ProxyRoute method shown in twitchax's answer. (Could have easily been a mistake on my end.)
Instead I defined the mapping in Statup.cs Configure() method similar to the code below.
app.UseProxy("api/someexternalapp-proxy/{arg1}", async (args) =>
{
string url = "https://someexternalapp.com/" + args["arg1"];
return await Task.FromResult<string>(url);
});
Piggy-backing on James Lawruk's answer https://stackoverflow.com/a/54149906/6596451 to get the twitchax Proxy attribute to work, I was also getting a 404 error until I specified the full route in the ProxyRoute attribute. I had my static route in a separate controller and the relative path from Controller's route was not working.
This worked:
public class ProxyController : Controller
{
[ProxyRoute("api/Proxy/{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
This does not:
[Route("api/[controller]")]
public class ProxyController : Controller
{
[ProxyRoute("{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
Hope this helps someone!
Twitchax's answer seems to be the best solution at the moment. In researching this, I found that Microsoft is developing a more robust solution that fits the exact problem the OP was trying to solve.
Repo: https://github.com/microsoft/reverse-proxy
Article for Preview 1 (they actually just released prev 2): https://devblogs.microsoft.com/dotnet/introducing-yarp-preview-1/
From the Article...
YARP is a project to create a reverse proxy server. It started when we noticed a pattern of questions from internal teams at Microsoft who were either building a reverse proxy for their service or had been asking about APIs and technology for building one, so we decided to get them all together to work on a common solution, which has become YARP.
YARP is a reverse proxy toolkit for building fast proxy servers in .NET using the infrastructure from ASP.NET and .NET. The key differentiator for YARP is that it is being designed to be easily customized and tweaked to match the specific needs of each deployment scenario. YARP plugs into the ASP.NET pipeline for handling incoming requests, and then has its own sub-pipeline for performing the steps to proxy the requests to backend servers. Customers can add additional modules, or replace stock modules as needed.
...
YARP works with either .NET Core 3.1 or .NET 5 preview 4 (or later). Download the preview 4 (or greater) of .NET 5 SDK from https://dotnet.microsoft.com/download/dotnet/5.0
More specifically, one of their sample apps implements authentication (as for the OP's original intent)
https://github.com/microsoft/reverse-proxy/blob/master/samples/ReverseProxy.Auth.Sample/Startup.cs
Here is a basic implementation of Proxy library for ASP.NET Core:
This does not implement the authorization but could be useful to someone looking for a simple reverse proxy with ASP.NET Core. We only use this for development stages.
using System;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace Sample.Proxy
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(options =>
{
options.AddDebug();
options.AddConsole(console =>
{
console.IncludeScopes = true;
});
});
services.AddProxy(options =>
{
options.MessageHandler = new HttpClientHandler
{
AllowAutoRedirect = false,
UseCookies = true
};
options.PrepareRequest = (originalRequest, message) =>
{
var host = GetHeaderValue(originalRequest, "X-Forwarded-Host") ?? originalRequest.Host.Host;
var port = GetHeaderValue(originalRequest, "X-Forwarded-Port") ?? originalRequest.Host.Port.Value.ToString(CultureInfo.InvariantCulture);
var prefix = GetHeaderValue(originalRequest, "X-Forwarded-Prefix") ?? originalRequest.PathBase;
message.Headers.Add("X-Forwarded-Host", host);
if (!string.IsNullOrWhiteSpace(port)) message.Headers.Add("X-Forwarded-Port", port);
if (!string.IsNullOrWhiteSpace(prefix)) message.Headers.Add("X-Forwarded-Prefix", prefix);
return Task.FromResult(0);
};
});
}
private static string GetHeaderValue(HttpRequest request, string headerName)
{
return request.Headers.TryGetValue(headerName, out StringValues list) ? list.FirstOrDefault() : null;
}
public void Configure(IApplicationBuilder app)
{
app.UseWebSockets()
.Map("/api", api => api.RunProxy(new Uri("http://localhost:8833")))
.Map("/image", api => api.RunProxy(new Uri("http://localhost:8844")))
.Map("/admin", api => api.RunProxy(new Uri("http://localhost:8822")))
.RunProxy(new Uri("http://localhost:8811"));
}
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
I have had a MobileService running on Azure, and have decided to create a new service and migrate the code myself. The new service is of the new type called: Azure Mobile App Service.
Currently I have Authentication working, and can do migrations/update-database. I am following the TodoItem example. I now want to create my own Custom API, which easily worked on MobileService, but I cannot get it working on Azure Mobile App :/
I have followed these two links web-Api-routing and app-service-mobile-backend. And I now have the following:
I have created a new controller:
[MobileAppController]
public class TestController : ApiController
{
// GET api/Test
[Route("api/Test/completeAll")]
[HttpPost]
public async Task<ihttpactionresult> completeAll(string info)
{
return Ok(info + info + info);
}
}
In the mobileApp.cs I have added the below code according to backend:
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
Additionally I have installed the below package according to web-api-routing:
Microsoft.AspNet.WebApi.WebHost
and the call from the client:
string t = await App.MobileService.InvokeApiAsync<string,string>("Test/completeAll", "hej");
Debug shows, that it is the correct URL:
{Method: POST, RequestUri: 'https://xxxxxxx.azurewebsites.net/api/Test/completeAll',
Version: 1.1, Content: System.Net.Http.StringContent, Headers:{ X-ZUMO-FEATURES:
AT X-ZUMO-INSTALLATION-ID: e9b359df-d15e-4119-a4ad-afe3031d8cd5 X-ZUMO-AUTH:
xxxxxxxxxxx Accept: application/json User-Agent:
ZUMO/2.0 User-Agent: (lang=Managed; os=Windows Store; os_version=--; arch=Neutral; version=2.0.31125.0)
X-ZUMO-VERSION: ZUMO/2.0 (lang=Managed; os=Windows Store; os_version=--; arch=Neutral; version=2.0.31125.0)
ZUMO-API-VERSION: 2.0.0 Content-Type: application/json; charset=utf-8 Content-Length: 3}}
But keep getting: 404 (Not Found)
Debug Message "The request could not be completed. (Not Found)"
What am I missing :/ ?
Update
I have tried expanding the code in The mobileApp.cs, with:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration().MapApiControllers()
.ApplyTo(config);
config.MapHttpAttributeRoutes();
app.UseWebApi(config);
based on app-service-backend, however still no access :/
Update
I used fiddler2 to access the endpoint through a browser and got the following results:
Update Again
I have tried to create another minimal solution, but still get the same error. Are there any great tutorials that I can follow to achieve this functionality?
The positive feeling is slowly evaporating . . .
The question is also running now on msdn, I will update here if any information is shown there.
Update
Tested Lindas comment, and I can in fact access the value converter:
// Use the MobileAppController attribute for each ApiController you want to use
// from your mobile clients
[MobileAppController]
public class ValuesController : ApiController
{
// GET api/values
public string Get()
{
MobileAppSettingsDictionary settings = this.Configuration.GetMobileAppSettingsProvider().GetMobileAppSettings();
ITraceWriter traceWriter = this.Configuration.Services.GetTraceWriter();
string host = settings.HostName ?? "localhost";
string greeting = "Hello from " + host;
traceWriter.Info(greeting);
return greeting;
}
// POST api/values
public string Post()
{
return "Hello World!";
}
}
This I access using the both the post and get function:
string t = await App.MobileService.InvokeApiAsync<string, string>("values", null, HttpMethod.Post, null);
or
string t = await App.MobileService.InvokeApiAsync<string, string>("values", null, HttpMethod.Get, null);
But the code I pasted has no route so why can I access it using values? What would the path be to the original controller if did not use the route parameter?
Extra Information
I have now created a support ticket with Microsoft and will update with additional information. . . Hopefully.
Update
Info from MSDN Forum: try MS_SkipVersionCheck
Reading about the attribute here, it does not seem applicable. But I tried it. Still Not Found for my API but the original one is still working. So it did not have an impact on this issue.
Yes !!!
So I finally got it working, I copied the usings from lidydonna - msft git and read about .net backend for mobileservice.
This ended with the following:
using System.Web.Http;
using Microsoft.Azure.Mobile.Server.Config;
using System.Threading.Tasks;
using System.Web.Http.Tracing;
using Microsoft.Azure.Mobile.Server;
namespace BCMobileAppService.Controllers
{
[MobileAppController]
public class TestController : ApiController
{
// GET api/Test
[HttpGet, Route("api/Test/completeAll")]
public string Get()
{
MobileAppSettingsDictionary settings = this.Configuration.GetMobileAppSettingsProvider().GetMobileAppSettings();
ITraceWriter traceWriter = this.Configuration.Services.GetTraceWriter();
string host = settings.HostName ?? "localhost";
string greeting = "Hello from " + host;
traceWriter.Info(greeting);
return greeting;
}
// POST api/values
[HttpPost, Route("api/Test/completeAll")]
public string Post(string hej)
{
string retVal = "Hello World!" + hej;
return retVal;
}
}
}
This is a new controller and not the one that comes with it as lidydonna used. It seemed like it wants both functions get and post. This resulted in the API was registered and could be accessed. This means the client call to the server I used was:
t = await App.MobileService.InvokeApiAsync<string, string>("Test/completeAll", null, HttpMethod.Post, new Dictionary<string, string>() { { "hej", " AWESOME !" }});
dialog = new MessageDialog(t);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
AND I GOT A RESPONSE YAY!!
Extra Information
The controllers that you create, i.e. the class needs to end with Controller, you can have text before but not after. This information was given on a MSDN forum discussion.
If the post and the get has the same input the server returns Not found. Having different inputs solves the issue.
In case of weird Internal Server Error, i.e. weird you can step through the entire server code all variables that you want to return are initialized, but the client receives the error. Then refer to Internal Server Error - Azure App Service Custom Controller where simple fix to the configuration can solve the issue.
You must have something wrong in your project configuration. I have a working sample here: https://gist.github.com/lindydonna/6fca7f689ee72ac9cd20
After creating the HttpConfiguration object, call config.MapHttpAttributeRoutes(). I added the route attribute [Route("api/Test/completeAll")] and I can confirm that the route is registered correctly.
Try adding this attribute to the ValuesController and check the route.
I found another cause for the 404 errors when it came to use attribute routing.
The code above originally had this in mobileApp.cs:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration().MapApiControllers()
.ApplyTo(config);
config.MapHttpAttributeRoutes();
app.UseWebApi(config);
The config.MapHttpAttributeRoutes() needs to be moved above the .ApplyTo:
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
new MobileAppConfiguration()
.UseDefaultConfiguration().MapApiControllers()
.ApplyTo(config);
Try switching inheriting from ApiController to TableController.
It is really strange but simple API request is not working in azure app service
So I have figure out solution which has worked for me. I have tested http requests with c# http post/get, android post/get, and objective C post/get
So first of all you need to update your Startup.MobileApp.cs class :
new MobileAppConfiguration()
.UseDefaultConfiguration()
.MapApiControllers() /* /api endpoints **missing part***/
.ApplyTo(config);
Then create Azure Mobile App Custom Controller. After that modify little bit your controller to get proper json response
public class Mes
{
public string message { get; set; }
}
// GET api/My
public Mes Get()
{
return new Mes { message = "thanks" };
// return "Hello from custom controller!";
}
// POST api/My
public Mes Post(Mes chal)
{
return new Mes { message = chal.message + "asnwer" };
// return "Hello from custom controller!";
}
}
You can simple leave first variant and get response, but OBjective C will say to you that JSON text did not start with array or object and option to allow fragments...and so on.. This happens because you getting simple string not object. So that is why I have modified my response with class Mes
But it is also depends how you make request and what type of object you expect.
So .MapApiControllers() it is the main key for API and WEB API controller is now changed to azure custom controller.
Hope this helps.