I have the following code which takes in a object of type employee based on it's model, I want to convert this to a DocumentDB Document then post to the database.
How would I do the conversion?
[HttpPost]
public async Task Post([FromBody]Employee employee)
{
using (_logger.BeginScope("Post employee"))
{
try
{
// convert employee to Document??
await _documentDbRepository.CreateItemsAsync(document);
}
catch (Exception e)
{
_logger.LogError(e.Message);
throw;
}
}
}
It seems that you're using a custom layer over top of the regular Cosmos libraries which is hard coded to only accept a Document. The libraries supplied by Microsoft are capable of inserting any generic object and will use default JSON serialization to turn it into a Document for you at insert time. Changing the signature on your custom repository to accept Object instead of Document should get you unblocked.
Related
I have a Asp.Net 6+ Web Api that has two endpoints doing almost exactly the same thing :
- the first one gets its parameters automagically from Asp.Net . I didn't give it a second thought: it accepts parameters from the POST's body and it's Asp.Net that does the deserialization, via System.Text.Json internally.
[HttpPost]
[Route("public/v1/myRoute/")]
public async Task<ActionResult> Import(IEnumerable<JsonItemModel> items) {
// the items are already ready to use.
FooProcessItems(items);
}
- the second one receives an IFormFile in a form data (the end-user uploads a file by using a button in the UI), gets the stream, and deserializes it "manually" using System.Text.JsonSerializer.DeserializeAsync.
[HttpPost]
[Route("public/v1/myRouteWithFile/")]
public async Task<ActionResult<Guid>> ImportWithFile([FromForm] MyFormData formData
) {
var stream = formaData.File.OpenReadStream();
var items = await JsonSerializer.DeserializeAsync<IEnumerable<JsonItemModel>>(file);
FooProcessItems(items);
}
My question :
I want to customize the deserialization process (to add some constraints such as "this field cannot be null", etc.) and I want both methods to produce exactly the same result.
How do I do that?
Is it simply a case of adding Json decorators in the model and letting .Net do the rest?
public class JsonItemModel {
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] // <-- Some custom constraint that will be picked up both by Deserialize and the POST endpoint.
public int SomeField { get; init; } = 0;
...
}
I'd like to return a data object that contains the details of the error with a BadRequestErrorMessageResult or BadRequestErrorMessageResult object like so:
public IHttpActionResult Action(Model model)
{
var validationResult = model.Validate();
if (validationResult.Successful)
{
// this one's okay; it supports sending data with a 200
return Ok(validationResult);
}
else
{
// However, how do I return a custom data object here
// like so?
// No such overload, I wish there was
// return BadRequest(validationResult);
}
}
The only three overloads of the ApiController.BadRequest() method are:
1. BadRequest();
2. BadRequest(string message);
3. BadRequest(ModelStateDictionary modelState);
Even with #3, a model state dictionary is ultimate a deep collection with one layer upon another, at the bottom of which, though, is a bunch of KeyValuePair<string, ModelError> where each ModelError also only has either a string or an Exception object.
Therefore, even with #3, we are only able to pack a string to send and not a custom object like I want to.
I am really not asking how I may go about working a hack or a kludge around the situation. My question is: is there an overload or another way baked into the .NET API to send an object to the client with a Bad Request HTTP status code?
I am using ASP.NET Web API version 5.2.4 targeting .NET Framework version 4.6.1.
You can use the Content<T>(...) method to do this. It returns a NegotiatedContentResult, which is serialized depending on the request headers (e.g. json, xml), and allows you to specify a HttpStatusCode.
You can use it like this:
return Content(HttpStatusCode.BadRequest, myObject);
If you wanted to, you could create your own BadRequest<T>(T obj) method in the controller as a wrapper, so then you could call it as you wanted:
public IHttpActionResult BadRequest<T>(T obj)
{
return Content(HttpStatusCode.BadRequest, obj);
}
public IHttpActionResult Action()
{
// do whatever validation here.
var validationResult = Validate();
// then return a bad request
return BadRequest(validationResult);
}
You can build/format the string in JSON format, pass it as string in the BadRequest() parameter and convert it to JSON again or any object on the caller's backend.
Haven't tried that but that should work.
Edit: Issue on GitHub here.
Playing around with Nancy for the first time and wrote this simple endpoint to test content negotiation based on Accept header:
public HomeModule()
{
Get["/"] = _ => new { Foo = "Bar" };
}
Using Postman, I set Accept: application/json and the result is as expected, while Accept: text/xml yields the text:
There was an error generating XML document
After some trial and error I found that this is caused by the anonymous type, and this is a separate issue concerning the XmlSerializer. However, I can't figure out how to capture this serialization error anywhere. It's like it's "swallowed" somewhere in Nancy or ASP.NET. The above message is returned as text to the requester with status code 200 OK.
Despite having setup Visual Studio to break on all exceptions as well as hooking up to pipelines.OnError and Application_OnError, I get no indication that an error occurred. I'm uncertain whether this is a problem with the serializer in general, ASP.NET or Nancy (or if I'm missing something obvious).
// in bootstrapper's ApplicationStartup method:
pipelines.OnError += (ctx, ex) => {
System.Diagnostics.Trace.WriteLine(ex?.ToString()); // doesn't fire
return null;
};
// in Global.asax:
protected void Application_Error(object sender, EventArgs e)
{
var err = Server.GetLastError();
System.Diagnostics.Trace.WriteLine(err?.Message);
}
Why is this error not thrown/capturable?
Nancy uses .Net XmlSerializer to XML serializations. And .Net XmlSerliazer cannot serialize anonymous types:
https://github.com/NancyFx/Nancy/wiki/Extending-Serialization-with-Converters
Nancy uses the .NET Framework's own built-in XmlSerializer
infrastructure to handle clients sending and receiving data using XML
as the transport format.
Can I serialize Anonymous Types as xml?
Sorry, you cannot. The XML Serializer pretty much just serializes public read-write types.
You will need to either return a POCO (Plain-Old-CSharp-Object) like this
public class HomeModule:NancyModule
{
public class FooModel
{
public string Foo { get; set; }
}
public HomeApi()
{
Get("/", p =>
{
var r = new F { Foo = "Bar" };
return r;
});
}
}
or implement another XmlSerialiser
https://github.com/NancyFx/Nancy/wiki/Extending-Serialization-with-Converters
The design of XmlSerializer is quite
extensible, and the way in which it is extensible is different from
the JavaScriptConverter and JavaScriptPrimitiveConverter types that
JSON serialization employs. XmlSerializer is unaware of JSON
converters, and JavaScriptSerializer ignores XML-specific attributes.
Thus, extensions to XML serialization and to JSON serialization can
coexist in the same project without interfering with one another.
Based on Nancy's internals. The error message from Serialiser is written to the output i.e There was an error generating the XML document.. But details of the exception are not written anywhere.
https://github.com/NancyFx/Nancy/blob/master/src/Nancy/Responses/DefaultXmlSerializer.cs
catch (Exception exception)
{
if (this.traceConfiguration.DisplayErrorTraces)
{
var bytes = Encoding.UTF8.GetBytes(exception.Message);
outputStream.Write(bytes, 0, exception.Message.Length);
}
}
In order to see the error, you need to workaround it by turning off Just-My-Code in Visual Studio. You can do this by the following steps:
Debug->Options and then Uncheck Just-My-Code
And then go to Debug->Windows-Exception Settings (Ctrl+Alt+E). Check Common Language Runtime Exceptions
Is there a way to configure App Insights to collect the operation name when monitoring a WCF service? All requests get lumped together by URL (which are just POSTs that end in .svc), so there is no easy way to determine which particular operation was called on the service.
Does there need to be a custom Telemetry Initializer that can somehow determine which operation was actually called and set a custom property? if so, how do you determine the current WCF operation name?
Another option for collecting data on WCF operations is to use the Microsoft.ApplicationInsights.Wcf Nuget package. You can read more about this here.
Brett,
Operation name can be customized in two ways:
1) Using a custom telemetry initializer - that specifically sets operation name.
For more information about telemetry initializers: Custom Telemetry Initializers
2) From sdk version 2-beta3, auto-generated request telemetry is accessible though HttpContext extension method:
System.Web.HttpContextExtension.GetRequestTelemetry
Once the request telemetry is retrieved, operation name associated with it can be changed.
Please let me know if this addressed your question.
Thanks,
Karthik
If you want to get the name of the WCF method called from a client in application insight you can use the following ITelemetryInitializer
With .net 5.0, the httprequest object is stored in the raw object properties of the telemetry context.
public class SoapActionHeaderTelemetryInitializer : ITelemetryInitializer
{
private static readonly Regex _soapActionUri = new Regex("^\"(?<uri>.*)\"$", RegexOptions.Compiled);
public void Initialize(ITelemetry telemetry)
{
DependencyTelemetry httpDependency = telemetry as DependencyTelemetry;
if (httpDependency != null)
{
httpDependency.Context.TryGetRawObject("HttpRequest", out var request);
if (request is HttpRequestMessage httpRequest)
{
if (httpRequest.Headers.TryGetValues("SOAPAction", out var values) && values.Any())
{
// SOAP Action is contained within quote : https://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383528
var soapAction = _soapActionUri.Match(values.First()).Groups["uri"].Value;
telemetry.Context.GlobalProperties["SOAPAction"] = soapAction;
}
}
}
}
}
Would like to try using AsyncCTP with TFS. Currently have a long running method that calls RunQuery on a TFS Query instance.
Query exposes the APM methods BeginQuery() and EndQuery(). As I understand it, the recommended approach to wrap these using AsyncCTP is something like: (example from docs)
Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead, buffer, offset, count, null);
Further, have wrapped it in an extension method as in the docs so my actual method looks like:
public static Task<WorkItemCollection> RunQueryAsync(this Query query)
{
if (query== null)
throw new ArgumentNullException("Query");
return Task<WorkItemCollection>.Factory.FromAsync(query.BeginQuery, query.EndQuery, null);
}
...but this fails to compile. Getting an "invalid argument" intellisense error that, frankly, I can't really understand because the types and format look correct. One possible issue might be that the Query APM methods expect an ICanceleableAsyncResult whereas the Task factory is expecting an IAsyncResult -- but looking at the TFS API, ICanceleableAsyncResult is a specialization of IAsyncResult.
Not sure whether i'm doing it wrong or its just not possible. Would love to be able to do it the AsyncCTP way but may have to go back to the APM pattern -- ugh!
Update: My Nito.AsyncEx library now includes a TeamFoundationClientAsyncFactory type, which can be used instead of rolling your own implementation below.
The TFS API is not strictly following the APM pattern because it does not take a state parameter, and this is preventing the built-in TaskFactory.FromAsync from working.
You'll have to write your own FromAsync equivalent, which can be done using TaskCompletionSource:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.Client;
public static class TfsUtils<TResult>
{
public static Task<TResult> FromTfsApm(Func<AsyncCallback, ICancelableAsyncResult> beginMethod, Func<ICancelableAsyncResult, TResult> endMethod, CancellationToken token)
{
// Represent the asynchronous operation by a manually-controlled task.
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
try
{
// Begin the TFS asynchronous operation.
var asyncResult = beginMethod(Callback(endMethod, tcs));
// If our CancellationToken is signalled, cancel the TFS operation.
token.Register(asyncResult.Cancel, false);
}
catch (Exception ex)
{
// If there is any error starting the TFS operation, pass it to the task.
tcs.TrySetException(ex);
}
// Return the manually-controlled task.
return tcs.Task;
}
private static AsyncCallback Callback(Func<ICancelableAsyncResult, TResult> endMethod, TaskCompletionSource<TResult> tcs)
{
// This delegate will be invoked when the TFS operation completes.
return asyncResult =>
{
var cancelableAsyncResult = (ICancelableAsyncResult)asyncResult;
// First check if we were canceled, and cancel our task if we were.
if (cancelableAsyncResult.IsCanceled)
tcs.TrySetCanceled();
else
{
try
{
// Call the TFS End* method to get the result, and place it in the task.
tcs.TrySetResult(endMethod(cancelableAsyncResult));
}
catch (Exception ex)
{
// Place the TFS operation error in the task.
tcs.TrySetException(ex);
}
}
};
}
}
You can then use it in extension methods as such:
using System.Threading;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
public static class TfsExtensions
{
public static Task<WorkItemCollection> QueryAsync(this Query query, CancellationToken token = new CancellationToken())
{
return TfsUtils<WorkItemCollection>.FromTfsApm(query.BeginQuery, query.EndQuery, token);
}
}