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
Related
I am refactoring an ASP.NET WEB API solution that uses Odata.
When dealing with errors I would like to provide a custom error payload which is defined in my CustomException class.
The issue is that when I make a bad request the generated response is the ODataException error payload which contains some confidential information that I don't want exposed and also the stack trace.
I need to modify this Odata payload and replace it with my own.
So far what I've tried is to use Exception Filters applied on Controller level and also tried to register an Exception Handler on global level. None of these worked.
Any help would be greatly appreciated.
I was able to resolve it with Exception Filter, I was using the wrong method before:
public class CustomExceptionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
ErrorContentResponse error = new ErrorContentResponse();
var response = new HttpResponseMessage(actionExecutedContext.Response.StatusCode)
{
Content = new ObjectContent<ErrorContentResponse>(error, new JsonMediaTypeFormatter())
};
actionExecutedContext.Response = response;
}
}
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.
I'm using Serilog and Seq in a .net core web api to handle the logging. I've added a reference to Steve Gordon's CorrelationId Middleware to get the X-Correlation-ID from the headers (or create a new one) to update the TraceIdentifier.
I've then added another middleware class to push the correlation id into the log context, but this isn't working correctly:
public class LogCorrelationIdMiddleware
{
private readonly RequestDelegate _next;
public LogCorrelationIdMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
// doesn't work - blank CorrelationId in Seq
using (LogContext.PushProperty("CorrelationId", context.Request.HttpContext.TraceIdentifier)) { return _next(context); }
// works - correct CorrelationId2 in Seq
//using (LogContext.PushProperty("CorrelationId2", context.Request.HttpContext.TraceIdentifier)) { return _next(context); }
}
}
Any ideas why?
It's working for me when I do the following (not as middleware):
using (LogContext.PushProperty("CorrelationId", "This is a test..."))
{
Log.Write(level, exception, messageTemplate, propertyValues);
}
So I suspect that it's your CorrelationId being overwritten by ASP.NET Core MVC as eluded to here:
https://github.com/serilog/serilog-aspnetcore/issues/59
Some background to the issue and their plan to deal with it in 3.0 is also here:
https://github.com/aspnet/AspNetCore/issues/5918
Apologies that this isn't a "fix", however I had the same issue and was able to work around it by adding a level of indirection between the log call (in my code) and the call to Log.Write (as shown above).
Going through the learning curve, and came across this scenario:
Given that 90% of the calls are JSON, added a GSON decoder when building the client. However, there are some method calls in the interface that should support raw return without decoding.
#RequestLine("GET /rest/myrawmethod")
String getRawMethod();
Currently since GSON is added as a decoder, instead of returning the raw string it attempts to decode it (it does look like JSON content, but I want to bypass decoding). I can't seem to find an easy way to disable for specific interface methods when not to use the GSON decoder as the exception.
Thanks!
Saw some references to various approaches, this seems like the best avenue at this time:
#RequestLine("GET /rest/myrawmethod")
feign.Response getRawMethod();
Then when you go to parse the response, use something like:
feign.codec.Decoder dc = new Decoder.Default();
String strresponse = dc.decode(myfeignresponse, String.class); //wrapped with exception handling
Good way to prototype in scenarios where you don't have anything around the REST payload, only the method calls...or want to do something more exotic (like use the feign.Response streaming methods).
Try making a custom Decoder like this:
class StringHandlingDecoder implements Decoder {
private final Decoder defaultDecoder;
StringHandlingDecoder(Decoder defaultDecoder) {
this.defaultDecoder = defaultDecoder;
}
#Override
public Object decode(Response response, Type type) throws IOException, FeignException {
if (type == String.class) {
return new StringDecoder().decode(response, type);
} else {
return this.defaultDecoder.decode(response, type);
}
}
}
Then build your client like this:
Feign.builder()
.decoder(new StringHandlingDecoder(new GsonDecoder()))
.build();
I want to write a unit test that verifies my route registration and ControllerFactory so that given a specific URL, a specific controller will be created. Something like this:
Assert.UrlMapsToController("~/Home/Index",typeof(HomeController));
I've modified code taken from the book "Pro ASP.NET MVC 3 Framework", and it seems it would be perfect except that the ControllerFactory.CreateController() call throws an InvalidOperationException and says This method cannot be called during the application's pre-start initialization stage.
So then I downloaded the MVC source code and debugged into it, looking for the source of the problem. It originates from the ControllerFactory looking for all referenced assemblies - so that it can locate potential controllers. Somewhere in the CreateController call-stack, the specific trouble-maker call is this:
internal sealed class BuildManagerWrapper : IBuildManager {
//...
ICollection IBuildManager.GetReferencedAssemblies() {
// This bails with InvalidOperationException with the message
// "This method cannot be called during the application's pre-start
// initialization stage."
return BuildManager.GetReferencedAssemblies();
}
//...
}
I found a SO commentary on this. I still wonder if there is something that can be manually initialized to make the above code happy. Anyone?
But in the absence of that...I can't help notice that the invocation comes from an implementation of IBuildManager. I explored the possibility of injecting my own IBuildManager, but I ran into the following problems:
IBuildManager is marked internal, so I need some other authorized derivation from it. It turns out that the assembly System.Web.Mvc.Test has a class called MockBuildManager, designed for test scenarios, which is perfect!!! This leads to the second problem.
The MVC distributable, near as I can tell, does not come with the System.Web.Mvc.Test assembly (DOH!).
Even if the MVC distributable did come with the System.Web.Mvc.Test assembly, having an instance of MockBuildManager is only half the solution. It is also necessary to feed that instance into the DefaultControllerFactory. Unfortunately the property setter to accomplish this is also marked internal (DOH!).
In short, unless I find another way to "initialize" the MVC framework, my options now are to either:
COMPLETELY duplicate the source code for DefaultControllerFactory and its dependencies, so that I can bypass the original GetReferencedAssemblies() issue. (ugh!)
COMPLETELY replace the MVC distributable with my own build of MVC, based on the MVC source code - with just a couple internal modifiers removed. (double ugh!)
Incidentally, I know that the MvcContrib "TestHelper" has the appearance of accomplishing my goal, but I think it is merely using reflection to find the controller - rather than using the actual IControllerFactory to retrieve a controller type / instance.
A big reason why I want this test capability is that I have made a custom controller factory, based on DefaultControllerFactory, whose behavior I want to verify.
I'm not quite sure what you're trying to accomplish here. If it's just testing your route setup; you're way better off just testing THAT instead of hacking your way into internals. 1st rule of TDD: only test the code you wrote (and in this case that's the routing setup, not the actual route resolving technique done by MVC).
There are tons of posts/blogs about testing a route setup (just google for 'mvc test route'). It all comes down to mocking a request in a httpcontext and calling GetRouteData.
If you really need some ninja skills to mock the buildmanager: there's a way around internal interfaces, which I use for (LinqPad) experimental tests. Most .net assemblies nowadays have the InternalsVisibleToAttribute set, most likely pointing to another signed test assembly. By scanning the target assembly for this attribute and creating an assembly on the fly that matches the name (and the public key token) you can easily access internals.
Mind you that I personally would not use this technique in production test code; but it's a nice way to isolate some complex ideas.
void Main()
{
var bm = BuildManagerMockBase.CreateMock<MyBuildManager>();
bm.FileExists("IsCool?").Dump();
}
public class MyBuildManager : BuildManagerMockBase
{
public override bool FileExists(string virtualPath) { return true; }
}
public abstract class BuildManagerMockBase
{
public static T CreateMock<T>()
where T : BuildManagerMockBase
{
// Locate the mvc assembly
Assembly mvcAssembly = Assembly.GetAssembly(typeof(Controller));
// Get the type of the buildmanager interface
var buildManagerInterface = mvcAssembly.GetType("System.Web.Mvc.IBuildManager",true);
// Locate the "internals visible to" attribute and create a public key token that matches the one specified.
var internalsVisisbleTo = mvcAssembly.GetCustomAttributes(typeof (InternalsVisibleToAttribute), true).FirstOrDefault() as InternalsVisibleToAttribute;
var publicKeyString = internalsVisisbleTo.AssemblyName.Split("=".ToCharArray())[1];
var publicKey = ToBytes(publicKeyString);
// Create a fake System.Web.Mvc.Test assembly with the public key token set
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = "System.Web.Mvc.Test";
assemblyName.SetPublicKey(publicKey);
// Get the domain of our current thread to host the new fake assembly
var domain = Thread.GetDomain();
var assemblyBuilder = domain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
moduleBuilder = assemblyBuilder.DefineDynamicModule("System.Web.Mvc.Test", "System.Web.Mvc.Test.dll");
AppDomain currentDom = domain;
currentDom.TypeResolve += ResolveEvent;
// Create a new type that inherits from the provided generic and implements the IBuildManager interface
var typeBuilder = moduleBuilder.DefineType("Cheat", TypeAttributes.NotPublic | TypeAttributes.Class, typeof(T), new Type[] { buildManagerInterface });
Type cheatType = typeBuilder.CreateType();
// Magic!
var ret = Activator.CreateInstance(cheatType) as T;
return ret;
}
private static byte[] ToBytes(string str)
{
List<Byte> bytes = new List<Byte>();
while(str.Length > 0)
{
var bstr = str.Substring(0, 2);
bytes.Add(Convert.ToByte(bstr, 16));
str = str.Substring(2);
}
return bytes.ToArray();
}
private static ModuleBuilder moduleBuilder;
private static Assembly ResolveEvent(Object sender, ResolveEventArgs args)
{
return moduleBuilder.Assembly;
}
public virtual bool FileExists(string virtualPath) { throw new NotImplementedException(); }
public virtual Type GetCompiledType(string virtualPath) { throw new NotImplementedException(); }
public virtual ICollection GetReferencedAssemblies() { throw new NotImplementedException(); }
public virtual Stream ReadCachedFile(string fileName) { throw new NotImplementedException(); }
public virtual Stream CreateCachedFile(string fileName) { throw new NotImplementedException(); }
}