Json Web API binding to Model Sometimes Throws Exception - c#

I have a WebAPI (not sure what version but it was created in VS2017) that receives a Json string and binds it to a model in the controller. Most of the time this works just fine. Occasionally it will throw an exception the first time I try to access the class instance. It is clear the binding routines are failing but the exception message is no help: Object reference not set to an instance of an object. I am guessing that the binder is failing and not even creating an empty instance of my object.
I log the Json string before I call the API so I can review the string for issues. I have identified that certain unicode symbols (such as the Trademark TM) will cause a failure, so I am managing those.
But I have a couple of recent Json strings that are throwing the exception and I cannot figure out why. There are no unicode symbols that I can locate. Now my users are asking why this certain style of job is failing.
It seems that once the InputStream is read by the inner MVC binding routines, it can never be read again. The trick of:
HttpContext.Current.Request.InputStream.Position = 0;
string streamresult = new System.IO.StreamReader(HttpContext.Current.Request.GetBufferedInputStream()).ReadToEnd();
throws it own exception, saying call was made before "the internal storage was filled by the caller of HttpRequest.GetBufferedInputStream".
Is there a way to peek inside the binder and see what it is choking
on?
or Is there a way to get a better exception message?
or to capture and log the incoming data stream?

If you want to read the Request Body, you will need to enable rewind on its stream, otherwise the request body can be read only once since by default it's a forward-only stream.
If you are using ASP.NET Core MVC, you could enable the rewind on your startup code with:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Use(async (context, next) => { // <----
context.Request.EnableRewind();
await next();
});
app.UseMvc();
}
For your last point I can recommend the Audit.NET library with its Audit.WebApi extension.

Related

Why no developer exception page in asp.net 5 razor pages?

I have a simple asp.net 5 razor pages app which does not show developer exception page but shows this in the browser developer tools
The character encoding of the plain text document was not declared. The document will render with garbled text in some browser configurations if the document contains characters from outside the US-ASCII range. The character encoding of the file needs to be declared in the transfer protocol or file needs to use a byte order mark as an encoding signature.
After many iterations and debugging it turns out there was a simple typo in the sql query and instead of showing the developer error page, it was showing blank with the aforementioned error in the browser console !
Questions -
Is this normal/expected ?
any way to turn on "more" debugging to identify such errors rather than trial and error ?
environment -
Visual studio 2019, .net 5
db access using dapper v2.0.78
configure excerpts below !
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logg)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
else
{
app.UseDeveloperExceptionPage();
//app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
...
there are no try/catch handlers, the code is rather basic
in the razor page
public IEnumerable<gEmployee> ListRows { get; private set; }
DAL mDAL;
public string Message;
public void OnGet(int dID)
{
ListRows = mDAL.GetEmployees(dID);
Message = $"Got {ListRows.Count()} Rows";
}
this is how i figured out the error when the OnGet() would get called but 2nd line with Message = ListRows.Count would not get executed !!
in GetEmployees
public List<gEmployee> GetEmployees(int dID)
{
using (var conn = new SqlConnection(cx.DefaultConnection))
{
var sql = #"SELECT * from gEmployee ";
if (dID > 0)
sql += " WHERE dID = #dID ";
var ListRows = conn.Query<gEmployee>(sql, new { dID = dID}).ToList();
return ListRows;
}
}
Usually in these cases, the first approach should be trying to reproduce the behaviour on a small/clean project so you can rule out various scenarios.
As you saw, in your case it's the
services.AddDatabaseDeveloperPageExceptionFilter()
that is causing the problem.
As Microsoft says in the documentation
This should only be enabled in the Development environment.
The developer error page will display in the event of an exception being raised in your server-side code. Based on the console message, it is likely that your SQL typo did not cause an exception to be raised. Or, if it did, you might be hiding it in a try - catch block. You haven't actually shown the relevant code so this is pure speculation. Either way, it resulted in your view page trying to render something that the browser did not understand, so the browser let you know in the console.
If you are writing SQL, it is always useful to test it in SQL Server Management Studio prior to handing it over to your code to execute.
Is this normal/expected
You're working with an advanced framework (.net) for the web development, so, debugging is not as simple as other platforms (like PHP frameworks) unless you know enough about the the project dependencies and underlying details/behaviors. In this way, you may sometimes see none relevant or ambiguous errors. In my opinion, working with high level frameworks requires a greater level of knowledge and experience. In your case, primarily double check methods and attributes to be used deliberately as they can disable/disturb the built-in Exception Handler behavior where you said instead of showing the developer error page, it was showing blank with the aforementioned error in the browser console
any way to turn on "more" debugging to identify such errors rather
than trial and error ?
You can write a HandleError method containing one of the approaches available at Handle errors in ASP.NET Core to consume where you need it like this :
private void HandleError()
{
...
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
....
}
public void OnGet(string code)
{
//do stuff
HandleError();
//return
}
Please read the entire of MS Doc page in order to find out which one well fits in your case to unravel the main problem. After clearing the issue, you no more need to use above technique.

Web API middleware short circuiting unexpectedly

The following code is part of an error handling middleware, whose goal is to provide consistent formatting for the client even if an error is thrown.
I am trying to serialize a response as XML when the Accept header is set to application/xml, otherwise return JSON. This article helped get me started: https://www.devtrends.co.uk/blog/handling-errors-in-asp.net-core-web-api
if (context.Request.Headers["Accept"] == "application/xml")
{
context.Response.ContentType = "application/xml";
using (var stringwriter = new StringWriter())
{
var serializer = new XmlSerializer(response.GetType());
serializer.Serialize(stringwriter, response);
await context.Response.WriteAsync(stringwriter.ToString());
}
}
else {
context.Response.ContentType = "application/json";
var json = JsonConvert.SerializeObject(response);
await context.Response.WriteAsync(json);
}
The else block works as expected. If I set a breakpoint on the line where the XmlSerializer is declared, execution halts. If I set a breakpoint on the following line, the breakpoint is never hit; a response has already been sent to the client.
My middleware is configured like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStatusCodePagesWithReExecute("/error/{0}");
app.UseExceptionHandler("/error/500");
app.UseHsts();
app.UseMiddleware<ErrorWrappingMiddleware>();
app.UseMvc();
}
Why is a response returned to the client before context.Response.WriteAsync(stringwriter.ToString()); is called in the if block?
I figured out the problem, it was a silly mistake.
To test 500 errors, I was intentionally adding a property to a model that didn't have a corresponding column in the database. This throws a SqlException on an invalid column name. There was however a second exception I missed that told me exactly what the problem was:
ApiResponse cannot be serialized because it does not have a parameterless constructor.
This occurred in the call to serializer.Serialize(stringwriter, response). The example I referenced creates an ApiResponse class (the response I attempted to serialize). However this class does not have a parameterless constructor in the example I was learning from, and I wasn't aware this was necessary until I found this answer. This second exception halted execution, but since I forgot to turn on development mode and I was trying to throw an exception, I didn't notice the second one.

MVC and OData, side by side

We've got a set of MVC apis already in place, but I've been asked to put up OData versions. I'm trying to co-locate both sets in the same application to share things like custom role providers. I'm new to OData and so far MVC lessons don't seem to translate directly.
With MVC, we took the generic initialization and managed all the routes with RouteAttribute and I tried to go the same style using ODataRouteAttribute, but all my odata paths come up 404 and the $metadata query returns a 500 saying the configuration hasn't been initialized.
Here's the app start configuration chunk (ODataApiConfig added after the MVC one):
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
ODataApiConfig.Configure(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
GlobalConfiguration.Configuration.EnsureInitialized();
And here's what's in ODataApiConfig:
// OData v7 replacement for configuration.EnableCaseInsensitive(true);
configuration.MapODataServiceRoute("odata", "odata",
builder =>
builder.AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp => ODataRoutingConventions.CreateDefaultWithAttributeRouting("odata", configuration))
.AddService<ODataUriResolver>(ServiceLifetime.Singleton, sp => new CaseInsensitiveResolver())
);
ODataModelBuilder modeler = new ODataConventionModelBuilder();
modeler.EntitySet<Task>("Tasks");
configuration.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: modeler.GetEdmModel()
);
configuration.EnsureInitialized();
And here's an example of an api I'm trying to hook up (to get a list of tasks assigned to an employee):
[HttpGet]
[EnableQuery]
[ODataRoute("v0.1/employee/{userGuid}/tasks")]
[ODataAuthorize(Roles = "Assignee")]
public IHttpActionResult GetMyTasks([FromODataUri]string userGuid)
{
...
}
So I'm puzzled why
a) A call to ~/odata/$metadata throws a "The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code." I tried calling EnsureInitialized() at the end of both the OData/MVC initialization and inside the OData initialize routine and it still throws the same error.
b) My ~/odata/employees/{userGuid}/tasks path comes up 404.
Any pointers will be appreciated.
Thanks
EDIT: It appears that the ~/odata/$metadata exception is related to the use of [ODataRoute] attributes. That seems to be enabled by default (and causing the exception); when I tried explicitly creating a routing convention object and DI-ing it, the $metadata exception went away. Didn't produce much useful, but the exception went away.
The stack trace on the exception is below; I tried breaking on any InvalidOperationExceptions thrown but did not get a break in the debugger
{"Message":"An error has occurred.","ExceptionMessage":"The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code.","ExceptionType":"System.InvalidOperationException","StackTrace":" at System.Web.OData.Routing.Conventions.AttributeRoutingConvention.get_AttributeMappings()
at System.Web.OData.Routing.Conventions.AttributeRoutingConvention.SelectController(ODataPath odataPath, HttpRequestMessage request)
at System.Web.OData.Routing.ODataPathRouteConstraint.SelectControllerName(ODataPath path, HttpRequestMessage request)
at System.Web.OData.Routing.ODataPathRouteConstraint.Match(HttpRequestMessage request, IHttpRoute route, String parameterName, IDictionary`2 values, HttpRouteDirection routeDirection)
at System.Web.Http.Routing.HttpRoute.ProcessConstraint(HttpRequestMessage request, Object constraint, String parameterName, HttpRouteValueDictionary values, HttpRouteDirection routeDirection)
at System.Web.Http.Routing.HttpRoute.ProcessConstraints(HttpRequestMessage request, HttpRouteValueDictionary values, HttpRouteDirection routeDirection)\r\n at System.Web.Http.Routing.HttpRoute.GetRouteData(String virtualPathRoot, HttpRequestMessage request)
at System.Web.Http.WebHost.Routing.HttpWebRoute.GetRouteData(HttpContextBase httpContext)"}
EDIT: At least I've got an answer on the $metadata call.
My initialization sequence had been informed by this article : https://blogs.msdn.microsoft.com/davidhardin/2014/12/17/web-api-odata-v4-lessons-learned/
which said that you had to initialize the Web Api calls first. I found the opposite. The above exception came as a result of initializing Web Api first. When I flipped them, the $metadata call started returning my Task entity set.
Unfortunately, my [ODataRoute] declarations in my TasksController still aren't getting routed.
Okay, the issue seems to be that some of the advice in here is out-dated:
https://blogs.msdn.microsoft.com/davidhardin/2014/12/17/web-api-odata-v4-lessons-learned/
Specifically, the $metadata exception turned out to be caused by setting up OData after Web API (per the above article). You have to initialize OData before Web Api.
Second, the point about controllers finding differentiation with the route prefix is wrong. I did segregate my web api controllers and odata controllers in different directories and I did use RoutePrefix/ODataRoutePrefix to put them on different paths.
But after getting the source code for AttributeRoutingConvention from github and using that for debugging, I found the following:
If you have 2 controllers with the same class name (e.g. both are called TaskController), even though they have different prefix paths and live in different directories, they collide. Both get dropped silently from the processing, so they are not found when a request comes in. Hence the 404.
So regardless of the prefixes and route attributes, the classes need to be named differently to be included.

Ensure that HttpConfiguration.EnsureInitialized()

I've installed Visual Studio 2013 and when I run my app I get the error below.
I've got no idea as to where I'm to initialized this object.
What to do?
Server Error in '/' Application.
The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[InvalidOperationException: The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code.]
System.Web.Http.Routing.RouteCollectionRoute.get_SubRoutes() +101
System.Web.Http.Routing.RouteCollectionRoute.GetRouteData(String virtualPathRoot, HttpRequestMessage request) +63
System.Web.Http.WebHost.Routing.HttpWebRoute.GetRouteData(HttpContextBase httpContext) +107
System.Web.Routing.RouteCollection.GetRouteData(HttpContextBase httpContext) +233
System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context) +60
System.Web.Routing.UrlRoutingModule.OnApplicationPostResolveRequestCache(Object sender, EventArgs e) +82
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +136
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +69
Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.18408
This is for AlumCloud
If you do it at the end of Application_Start it will be too late, as WebApiConfig.Register has been called.
The best way to resolve this is to use new initialization method by replacing in Global.asax :
WebApiConfig.Register(GlobalConfiguration.Configuration);
by
GlobalConfiguration.Configure(WebApiConfig.Register);
See #gentiane's answer below for the correct way to handle this now.
At the end of the Application_Start method in Global.Asax.cs try adding:-
GlobalConfiguration.Configuration.EnsureInitialized();
I actually got this error when I was using Attribute Routing within my WebApi.
I had
[Route("webapi/siteTypes/{siteTypeId"]
instead of
[Route("webapi/siteTypes/{siteTypeId}"]
for my route and got this error. I had simply missed out the closing curly bracket. Once I added it back in, this error didn't occur again.
This is old, but is the first result on google when searching for this error. After quite a bit of digging I was able to figure out what was going on.
tldr:
All GlobalConfiguration.Configure does is invoke your action and call EnsureInitialized(). config.MapAttributeRoutes() must be called before EnsureInitialized() since EnsureInitialized only runs once.
Meaning: if you're coming from an existing Mvc project, all you have to do is:
Add
GlobalConfiguration.Configuration.EnsureInitialized();
to the bottom of your Application_Start method.
OR
Move your entire configuration into a single call to GlobalConfiguration.Configure:
GlobalConfiguration.Configure(config =>
{
WebApiConfig.Register(config);
config.MapAttributeRoutes();
...
});
Digging Deeper
HttpConfiguration.Configuration has an "Initializer" property defined like this:
public Action<HttpConfiguration> Initializer;
HttpConfiguration.EnsureInitialized() runs this action and sets _initialized to true
public void EnsureInitialized()
{
if (_initialized)
{
return;
}
_initialized = true;
Initializer(this);
}
HttpConfiguration.MapAttributeRoutes calls internal method AttributeRoutingMapper.MapAttributeRoutes which sets HttpConfiguration.Initializer
public static void MapAttributeRoutes(...)
{
RouteCollectionRoute aggregateRoute = new RouteCollectionRoute();
configuration.Routes.Add(AttributeRouteName, aggregateRoute);
...
Action<HttpConfiguration> previousInitializer = configuration.Initializer;
configuration.Initializer = config =>
{
previousInitializer(config);
...
};
}
GlobalConfiguration.Configure runs EnsureInitialized immediately after invoking your action:
public static void Configure(Action<HttpConfiguration> configurationCallback)
{
if (configurationCallback == null)
{
throw new ArgumentNullException("configurationCallback");
}
configurationCallback.Invoke(Configuration);
Configuration.EnsureInitialized();
}
Don't forget, if you run in to a wall, the source for asp.net is available at http://aspnetwebstack.codeplex.com/SourceControl/latest
I've had a related issue. Sometimes calling GlobalConfiguration.Configure multiple times triggers this error. As a workaround, I've put all configuration initialization logic in one place.
IF THIS ERROR SEEMS TO HAVE COME "OUT OF NOWHERE", i.e. your app was working perfectly fine for a while, ask yourself: Did I add an action to a controller or change any routes prior to seeing this error?
If the answer is yes (and it probably is), you likely made a mistake in the process. Incorrect formatting, copy/pasting an action and forgetting to make sure the endpoint names are unique, etc. will all end you up here. The suggestion that this error makes on how to resolve it can send you barking up the wrong tree.
For me, the problem was that I was trying to use named parameters for query string fields in my routes:
[Route("my-route?field={field}")]
public void MyRoute([FromUri] string field)
{
}
Query string fields are automatically mapped to parameters and aren't actually part of the route definition. This works:
[Route("my-route")]
public void MyRoute([FromUri] string field)
{
}
Although the above answer works if incase that is not set, In my case this stuff was set already. What was different was that, for one of the APIs I had written, I had prefixed the route with a / . Example
[Route("/api/abc/{client}")]
.Changing this to
[Route("api/abc/{client}")]
fixed it for me
Call
GlobalConfiguration.Configuration.MapHttpAttributeRoutes();
before
GlobalConfiguration.Configure(c => ...);
completes its execution.
I got this error when the version of Newtonsoft.Json was different in my main project compared to the helper project
One typically gets this exception when route templates in "Attribute Routing" are not proper.
For example, i got this when i wrote the following code:
[Route("{dirName:string}/contents")] //incorrect
public HttpResponseMessage GetDirContents(string dirName) { ... }
In route constraints syntax {parameter:constraint}, constraint by default is of type string. No need to mention it explicitly.
[Route("{dirName}/contents")] //correct
public HttpResponseMessage GetDirContents(string dirName) { ... }
I began getting this error one day. After I'd altered our app to call EnsureInitialized() I was able to see the root cause.
I had a custom attribute, a filter, on an action. That attribute class had had a breaking change made in the NuGet package in which it lives.
Even though I'd updated the the code and it all compiled, the local IIS worker was loading an old DLL and not finding a class member during initialization, reading attributes on actions etc.
For some reason (possibly due to order/when our logging is initialized), this error was not discoverable, possibly leaving the WebAPI in a strange state, until I'd added EnsureInitialized() which caught the exception and surfaced it.
Performing a proper bin and obj clean via a handy script resolved it.
In my case I created the webservice in project A and started it from Project B and I got exactly this error. The problem was that some .dll-files which are required by A where missing in the build-output-folder of B. Ensure that these .dll-files are available fixed it.
In my case , i used an Entity as parameter of my action that its 'Schema' is missing.
Wrong attribute:
[Table("Table name", Schema = "")]
Correct :
[Table("Table name", Schema = "schema name")]
In my case I fixed replacing:
<Route("{id:string}")>
with
<Route("{id}")>
Strange that I was sure that the original code was working before suddenly stopping, go figure....
I experienced this similar error.
<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code.</ExceptionMessage>
<ExceptionType>System.InvalidOperationException</ExceptionType>
<StackTrace> at System.Web.Http.Routing.RouteCollectionRoute.get_SubRoutes() at System.Web.Http.Routing.RouteCollectionRoute.GetRouteData(String virtualPathRoot, HttpRequestMessage request) at System.Web.Http.WebHost.Routing.HttpWebRoute.GetRouteData(HttpContextBase httpContext)</StackTrace>
</Error>
After fiddling with it for a while, I realized two problems: directory name collides with routing prefix and routing prefix must not end with '/'. When starting up the service for debugging I got this browser error after adding the SampleController.cs.
The folder structure
/api/Controllers/SampleController.cs
/api/Model/...
SampleController.cs
[RoutePrefix("api/sub/{parameterId}/more/")]
public class SampleController : ApiController
{
[HttpGet]
[Route("")]
public IHttpActionResult Get(int parameterId)
{
return Ok("Knock Knock...");
}
}
Changing the routing prefix to not end in '/' and not colliding with the directory name solved the problem.

How can I safely handle POST parameters in an HTTP Handler using C#?

I'm working on an ASP.Net C# application (my first!) that contains an HTTP Handler within it. My application works with several parameters that are passed in via the URL. My question(s) is as follows:
My applications entry point is via the HTTP Handler. When I enter my ProcessRequest method, I am assigning the values of the URL parameters to variables so that I may do something with the data.
Question: Is this safe, even if I am not setting the value to anything when I call the URL?
In my example: I call host/handler1.ashx instead of host/handler1.ashx?something=foo
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
string something = context.Request["something"];
context.Response.Write("Hello World: " + something);
}
When calling the above method using the plain URL with no parameters, it executes just fine, but the string something is just blank/null.
Additional questions:
What happens to the variable something in the case that I do not explicitly initialize it via the URL? I understand that it is null, but can this lead to problems?
Is it dangerous or not safe to call the plain URL (i.e. should I always call it with parameter values specified)?
What is the best way to call a "clean" ashx URL to start the application but not risk problems?
The application will do a series of subsequent GET redirects as it accumulates values and passes them back to the app via the query string.
Should I do a POST or GET upon initial call of the application?
Sorry for asking the same question multiple ways, but I'm a bit confused on the topic and this is my first time writing an app like this. Any patience and advice you could provide on how to safely handle and initialize parameters is greatly appreciated!
There is nothing wrong with omitting parameters to an endpoint. As the developer you are in charge of enforcing what the client is allowed send to you. If you expect a parameter and it's missing, throw an error (e.g. HttpException).
If you are creating or updating data (i.e. inserting or updating records in a database) the best method would be a POST or PUT.
Edit - Here is an example of how you can handle the input:
public void ProcessRequest(HttpContext context) {
//Maybe you require a value?
if (string.IsNullOrEmpty(context.Request["something"])) {
throw new HttpException(400, "You need to send a value!");
}
//Maybe you require a certain value?
if (context.Request["something"] != "beerIsGood") {
throw new HttpException(400, "You need to send the right value!");
}
}
You can't. The Internet is dangerous.

Categories

Resources