Servicestack.net custom XML DateTime serialization - datetime

Is there a way to override the XML DateTime serialization in Servicestack.Net like we can do with JSON:
JsConfig<DateTime>.SerializeFn = date => new DateTime(date.Ticks, DateTimeKind.Local).ToString("dd.MM.yyyy HH:mm:ss");
I want to do the same thing for all XML DateTimes.
Edit:
I just found out I can hook into the XML serialization with this:
this.ContentTypeFilters.Register("application/xml", SerializeXmlToStream, DeserializeXmlFromStream);
public static void SerializeXmlToStream(IRequestContext requestContext, object response, Stream stream)
{
using (var sw = new StreamWriter(stream))
{
sw.Write(response.ToXml());
}
}
This returns the same XML as without this hook. If someone has any idea how I can change the serialiazation of DateTime to a custom format with this or any other global way please let me know.

Related

Minimal API and XML formatters

Trying out minimal APIs in .NET 6 and can't make it work with XML content type. If I use standard controllers, using .AddXmlSerializerFormatters() extension does the job:
builder.Services.AddControllers().AddXmlSerializerFormatters();
But when I switch from controller to .MapPost(..), I start getting 415 HTTP responses.
app.MapPost("/endpoint", ([FromBody] Request request) => {})
.Accepts<Request>("text/xml");
HTTP response: 415 Microsoft.AspNetCore.Http.BadHttpRequestException: Expected a
supported JSON media type but got "text/xml"
Is there any other way I can declare XML formatters that will work with minimal APIs?
As suggested by the post linked by guru-stron, it's possible to pass XML documents by implementing your own wrapping model that provides a BindAsync method.
internal sealed class XDocumentModel
{
public XDocumentModel(XDocument document) => Document = document;
public XDocument Document { get; init; }
public static async ValueTask<XDocumentModel?> BindAsync(HttpContext context, ParameterInfo parameter)
{
if (!context.Request.HasXmlContentType())
throw new BadHttpRequestException(
message: "Request content type was not a recognized Xml content type.",
StatusCodes.Status415UnsupportedMediaType);
return new XDocumentModel(await XDocument.LoadAsync(context.Request.Body, LoadOptions.None, CancellationToken.None));
}
}
I added a extension method to HttpRequest for convenient Content-Type validation.
internal static class HttpRequestXmlExtensions
{
public static bool HasXmlContentType(this HttpRequest request)
=> request.Headers.TryGetValue("Content-Type", out var contentType)
&& string.Equals(contentType, "application/xml", StringComparison.InvariantCulture);
}
You can then use the model directly as a paramter by your minimal API endpoint.
app.MapGet("/xml-test", (XDocumentModel model) =>
{
// model.Document <- your passed xml Document
return Results.Ok(new { Value = model.Document.ToString() });
})
Some final thoughts: This implementation enables you to pass a generic XML document to the endpoint. However, if you expect a certain document structure, you could implement this by making the XDocumentModel expect a generic type parameter and extracting this type's properties from the XDocument instance.
I did it this way:
app.MapPost("/endpoint", (HttpContext c) =>
{
var reader = new StreamReader(c.Request.Body);
var xml = reader.ReadToEndAsync().Result;
// You can do with your xml string whatever you want
return Results.Ok();
}).Accepts<HttpRequest>("application/xml");

Can I disable model binding and use the raw request body in an action in dotnet core?

I want to setup an endpoint for testing webhooks from third parties. Their documentation is uniformly poor and there is no way ahead of time to tell exactly what I will be getting. What I've done is setup an ApiController that will just take a request and add a row to a table with what they are sending. This lets me at least verify they are calling the webhook, and to see the data so I can program to it.
// ANY api/webook/*
[Route("{*path}")]
public ActionResult Any(string path)
{
string method = Request.Method;
string name = "path";
string apiUrl = Request.Path;
string apiQuery = Request.QueryString.ToString();
string apiHeaders = JsonConvert.SerializeObject(Request.Headers);
string apiBody = null;
using (StreamReader reader = new StreamReader(Request.Body))
{
apiBody = reader.ReadToEnd();
}
Add(method, name, apiUrl, apiQuery, apiHeaders, apiBody);
return new JsonResult(new { }, JsonSettings.Default);
}
This works great, except for this new webhook I am usign that posts as form data so some middleware is reading the body and it ends up null in my code. Is there any way to disable the model processing so I can get at the request body?
You could actually use model binding to your advantage and skip all that stream reading, using the FromBody attribute. Try this:
[Route("{*path}")]
[HttpPost]
public ActionResult Any(string path, [FromBody] string apiBody)

Is there a way to force ASP.NET Web API to return plain text?

I need to get a response back in plain text from a ASP.NET Web API controller.
I have tried do a request with Accept: text/plain but it doesn't seem to do the trick.
Besides, the request is external and out of my control. What I would accomplish is to mimic the old ASP.NET way:
context.Response.ContentType = "text/plain";
context.Response.Write("some text);
Any ideas?
EDIT, solution:
Based on Aliostad's answer, I added the WebAPIContrib text formatter, initialized it in the Application_Start:
config.Formatters.Add(new PlainTextFormatter());
and my controller ended up something like:
[HttpGet, HttpPost]
public HttpResponseMessage GetPlainText()
{
return ControllerContext.Request.CreateResponse(HttpStatusCode.OK, "Test data", "text/plain");
}
Hmmm... I don't think you need to create a custom formatter to make this work. Instead return the content like this:
[HttpGet]
public HttpResponseMessage HelloWorld()
{
string result = "Hello world! Time is: " + DateTime.Now;
var resp = new HttpResponseMessage(HttpStatusCode.OK);
resp.Content = new StringContent(result, System.Text.Encoding.UTF8, "text/plain");
return resp;
}
This works for me without using a custom formatter.
If you explicitly want to create output and override the default content negotiation based on Accept headers you won't want to use Request.CreateResponse() because it forces the mime type.
Instead explicitly create a new HttpResponseMessage and assign the content manually. The example above uses StringContent but there are quite a few other content classes available to return data from various .NET data types/structures.
For .net core:
[HttpGet("About")]
public ContentResult About()
{
return Content("About text");
}
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/formatting
If you are just looking for a simple plain/text formatter without adding additional dependencies, this should do the trick.
public class TextPlainFormatter : MediaTypeFormatter
{
public TextPlainFormatter()
{
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
}
public override bool CanWriteType(Type type)
{
return type == typeof(string);
}
public override bool CanReadType(Type type)
{
return type == typeof(string);
}
public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext)
{
return Task.Factory.StartNew(() => {
StreamWriter writer = new StreamWriter(stream);
writer.Write(value);
writer.Flush();
});
}
public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
{
return Task.Factory.StartNew(() => {
StreamReader reader = new StreamReader(stream);
return (object)reader.ReadToEnd();
});
}
}
Don't forget to add it to your Global web api config.
config.Formatters.Add(new TextPlainFormatter());
Now you can pass string objects to
this.Request.CreateResponse(HttpStatusCode.OK, "some text", "text/plain");
Please be careful not to use context in ASP.NET Web API or you will sooner or later be sorry. Asynchronous nature of ASP.NET Web API makes using HttpContext.Current a liability.
Use a plain text formatter and add to your formatters. There are dozens of them around. You could even write yours easily. WebApiContrib has one.
You can force it by setting the content type header on httpResponseMessage.Headers to text/plain in your controller provided you have registered plain text formatter.
When Accept: text/plain doesnt work, then there is no registered formatter for text mime types.
You can ensure that there is no formatters for specified mime type by getting list of all supported formatters from service configuration.
Create a very straightforward media type formatter that support text mime types.
http://www.asp.net/web-api/overview/formats-and-model-binding/media-formatters
An extension like the following one can reduce the number of lines and beautify your code:
public static class CommonExtensions
{
public static HttpResponseMessage ToHttpResponseMessage(this string str)
{
var resp = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(str, System.Text.Encoding.UTF8, "text/plain")
};
return resp;
}
}
Now you can consume the defined extension in your Web API:
public class HomeController : ApiController
{
[System.Web.Http.HttpGet]
public HttpResponseMessage Index()
{
return "Salam".ToHttpResponseMessage();
}
}
By routing {DOMAIN}/api/Home/Index you can see the following plain text:
MyPlainTextResponse

Deserializing json data from jquery post method directly to string array

Is there a way to deserialize an array sent by jquery post method to directly c# string array(string[])?
I tried posting data like this
$.post(url,
{
'selectedTeams[]'=['Team1','Team2']
},
function(response) {}, 'json');
And trying to consume it like this in C# class
string jsonData = new StreamReader(context.Request.InputStream).ReadToEnd();
var selectedTeams = new JavaScriptSerializer().Deserialize<string[]>(jsonData);
It didn't work and ofcource it should not as there is no property selectedTeams[] in string[]
I am aware of the way to define a class something like this
class Teams
{
public string[] SelectedTeams{get;set;}
}
and then do the deserialization.
But I think that is an unnecessary defining a class so isn't there a way to directly convert json array to c# string array
Thanks in advance.
Figure it out!
Using stringified array object rather than direct named json parameter to pass as data solved my problem
I am now posting like this
var Ids = new Array();
Ids.push("Team1");
Ids.push("Team2");
$.post(url, JSON.stringify(Ids), function(response) {}, 'json');
And now able to deserialize json response directly to string array like this
string jsonData = new StreamReader(context.Request.InputStream).ReadToEnd();
var selectedTeams = new JavaScriptSerializer().Deserialize<string[]>(jsonData);
Thanks!!
you could develop your own class, but I would suggest you use this:
http://json.codeplex.com/

WCF rest service to accept dynamic as parameter

In my application, I am sending a json object to a service and at the service end, I expect an object of type dynamic
public bool TestService(dynamic entity)
{
// process
}
When i debug and see the entity filled, I am not able to type cast it. Any idea how i can retrieve fields from the entity sent
I'm curious - if you're sending up a JSON formatted object, why not have your service method accept a string and then use something like JSON.net to cast it to the appropriate type?
public bool TestService(string entity)
{
var myObject = JsonConvert.DeserializeObject<MyObjectType>(entity);
//do stuff with myObject...
}
Or you could deserialize it into an anonymous object:
public bool TestService(string entity)
{
var myAnonymousObject = new { Name = String.Empty, Address = String.Empty };
var myObject = JsonConvert.DeserializeAnonymousType(entity, myAnonymousObject);
//do stuff with myObject
}
I guess I'm not sure why your JSON formatted object needs to be dynamic.

Resources