I am working on making a WEB API post that takes in JSON and turns it into a model object so it can be put in the database.
[HttpPost]
[Route]
[SwaggerResponse(HttpStatusCode.Created, CreatedMessage)]
[SwaggerResponse(HttpStatusCode.BadRequest, BadRequestMessage, typeof(HttpError))]
[SwaggerResponse(HttpStatusCode.Unauthorized, UnauthorizedMessage, typeof(HttpError))]
[SwaggerResponse(HttpStatusCode.InternalServerError, UnknownErrorMessage, typeof(HttpError))]
public async Threading.Task<IHttpActionResult> PostDocument([FromBody] Api.Document documentModel)
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
// Put the files in a temporary location
// so then we can ReadAsMultiPartAsync and get access to the other data submitted
var tempPath = HttpContext.Current.Server.MapPath("~/App_Data/Temp/" + Guid.NewGuid());
Directory.CreateDirectory(tempPath);
var deserializer = new DataContractJsonSerializer(typeof(Api.Document));
HttpContext.Current.Request.InputStream.Position = 0;
Api.Document docModel = (Api.Document)deserializer.ReadObject(HttpContext.Current.Request.InputStream);
if (docModel != null)
{
// We don't have the json data so delete the TempFiles and return BadRequest
Directory.Delete(tempPath, true);
return ResponseMessage(Request.CreateResponse(HttpStatusCode.BadRequest));
}
return await PostOrStatusCodeAsync(docModel, RouteNames.GetById).ConfigureAwait(true);
}
However, the docModel is flagging a warning that it will always be null. Why would it be null after deserializing incoming json?
The key thing is this:
if (docModel != null)
{
// We don't have the json data so delete the TempFiles and return BadRequest
Directory.Delete(tempPath, true);
return ResponseMessage(Request.CreateResponse(HttpStatusCode.BadRequest));
}
return await PostOrStatusCodeAsync(docModel, RouteNames.GetById).ConfigureAwait(true);
}
If docModel is not null, you return with BadRequest. The place it is flagging it always being null is only reached if the if test is false. I suspect you have the wrong relational operator on your 'if' statement.
On line 197 it will always be null because if it's not it will enter on the if on line 190 which returns on line 194 thus never reaching line 197 if it's not null.
Related
I am trying to make CRUD functionality for the table Recipe. Everything works fine in the API, however, I am stuck at the Update part inside the MVC. Basically, when trying to access the view meant for updating, all of the fields should be already filled, compared to when I am creating a recipe from scratch.
public ActionResult CreateOrEdit(int id = 0)
{
if (id==0)
return View(new Recipe());
else
{
HttpResponseMessage response = GlobalVariablesUpdate.clientUp.GetAsync(id.ToString()).Result;
var temp = response.Content.ReadAsStringAsync().Result;
return View(JsonConvert.DeserializeObject<IList<Recipe>>(response.ToString()));
}
}
The code inside the else{}is meant to fill all the values after reading the content. Unfortunately, on line
return View(JsonConvert.DeserializeObject<IList<Recipe>>(response.ToString()));
I am getting the following error
'Unexpected character encountered while parsing value: S. Path '',
line 0, position 0.'
The temp variable's content looks like this:
"[{\"id\":5002,\"name\":\"Test Recipe\",\"recipeLink\":\"testlink\",\"category1Id\":7757,\"category2Id\":7758,\"category3Id\":7759,\"category4Id\":7760,\"recipeById\":1,\"totalTime\":30,\"totalTimeUnitId\":1,\"activeTime\":20,\"activeTimeUnitId\":1,\"instructions\":\"Test Instructions\",\"sourceKey\":\"Test SK\",\"recipeBy\":\"TestPerson\",\"insertedAtUtc\":\"2019-09-04T12:18:48.0466667\",\"isVerified\":1,\"numPersons\":5}]"
I assume the root of this problem is that my response variable has these backslashes, hence the unexpected character encounter. How can I get rid of this?
EDIT
.Replace(#"\", " ") doesn't work, so I assume that the backslash might not actually be the issue?
You are deserializing response.ToString(), but you should be deserializing temp:
var recipes = JsonConvert.DeserializeObject<IList<Recipe>>(temp);
return View(recipes);
Sidenotes: you should make your method async, to avoid deadlocks, and dispose your response:
public async Task<ActionResult> CreateOrEdit(int id = 0)
{
if (id==0)
{
return View(new Recipe());
}
using (var response = await GlobalVariablesUpdate.clientUp.GetAsync(id.ToString())
{
var temp = await response.Content.ReadAsStringAsync();
var recipes = JsonConvert.DeserializeObject<IList<Recipe>>(temp);
return View(recipes);
}
}
I have tried to retrieve data from SQL database. I am using Entity Framework core. Its retrieving the required data from the database. I can see the data coming when debugging but the data is not assigning to the variable of type var. FYI, the value of variable type is 0 and its basically an enum, i typecast it to int. Below is the code
public async Task<string> GetMailTemplateByType(Models.TemplateTypes type)
{
var mailTemplate = await _userDbContext.MailTemplates.FirstOrDefaultAsync(mt => mt.TemplateType==((int)type));
return mailTemplate.MailHtml;
}
Here is the definition:
var HtmlTemplate = await _coreDataManager.GetMailTemplateByType(TemplateTypes.Activation);
when debug with try catch, Its showing
Object reference not set to an instance of an object
what is the problem here?
We can see from your code that you recieve the following mail template object:
Id = {aeced541-7003-437e-8f77-4605766fb62c};
MailHtml = "Hi, Thank you so much for signing up.Here is Confirmation link to proceed further ...";
TemplateType = 0;
Here you are passing some TemplateType value we don't know
public async Task<string> GetMailTemplateByType(Models.TemplateTypes type)
{
Here you compare that type value to the TemplateType property in the MailTemplate object we see in the dubugger window
var mailTemplate = await _userDbContext.MailTemplates.FirstOrDefaultAsync(mt => mt.TemplateType==((int)type));
But if type is not 0, it will not return the MailTemplate object as the MailTemplate object we see in the debugger window has a TemplateType value of 0, thus FirstOrDefaultAsync will return a null value, see "fault returns NullReferenceException if no match is found"
public async Task<string> GetMailTemplateByType(Models.TemplateTypes type)
{
var mailTemplate = /*your expression from screenshot*/.FirstOrDefault();
if(mailTemplate = null)
throw new NullReferenceException();
return mailTemplate;
}
..........................
try
{
GetMailTemplateByType(TemplateTypesVariable);
}
catch(NullReferenceException err)
{
Console.WriteLine("template does not exist");
}
It looks like you are trying to receive data which does not exist.
Why do you even select the whole object? ef-core is just like sql, select what you need (in your case just do a
var mailTemplate = await _userDbContext.MailTemplates.Where(mt => mt.TemplateType==((int)type)).Select(x => x.MailHtml).FirstOrDefaultAsync();
but this will still not work, since your entity says that the TemplateType is 0 (and your enum starts with 1). Guessing you saved it wrong
wanted to write this as a comment but i just created this account
I have a controller method:
public async Task SaveRouting(string points, int tripId, decimal totalMileage)
{
if (Request.IsAjaxRequest())
{
//....
await _serviceTrip.UpdateTotalMileageAsync(tripId, totalMileage);
}
else
throw new Exception("Only ajax calls are allowed");
}
so, as we can see, this method returns Task, so nothing to client. But if something wrong (i.e. totalMileage is less or equal 0) I want to return 422 status code and dictionary with invalid data, like:
{ "totalMileage" : "Total mileage should be greater than 0" }
How to do it?
I try to do it:
if (totalMileage <= 0)
{
Response.StatusCode = 422; // 422 Unprocessable Entity Explained
}
but how to describe an error?
If you want to describe the error after setting Response.StatusCode, then you have to write into the body of the http response by calling Response.Body.Write(byte[],int, int).
Therefore, you can convert your response message to a byte array using the following method:
public byte[] ConvertStringToArray(string s)
{
return new UTF8Encoding().GetBytes(s);
}
And then use it like this:
byte[] bytes = ConvertStringToArray("Total mileage should be greater than 0");
Response.StatusCode = 422;
Response.Body.Write(bytes,0,bytes.Length);
But you can further simplify this using extension methods on ControllerBase
I don't know if it's a bug but i'm unable to get raw request on server side.
Consider following controller method:
[AllowAnonymous]
[Route("api/sayHello")]
[HttpPost]
public string SayHello([FromBody] string userName)
{
return $"Hello, {userName}.";
}
I call it via cUrl:
curl -X POST 'https://localhost:809/api/sayHello' --insecure -d "=userName"
It works fine.
Now I'm trying to add some logging. I add a global filter which is doing following:
public async Task LogFilterAction(HttpActionContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
LogFilterAction(context.ActionDescriptor?.ControllerDescriptor?.ControllerType,
context.ActionDescriptor?.ActionName,
context.Request?.RequestUri,
await GetDataAsString(context.Request?.Content),
context.Response?.StatusCode
);
}
private static async Task<string> GetDataAsString(HttpContent content)
{
if (content == null)
return null;
var contentBytes = await content.ReadAsByteArrayAsync();
return Encoding.UTF8.GetString(contentBytes);
}
But here is the problem: for unknown reason reason contentBytes are always an empty array. I see that it's actual length is 9 (it's length of =userName string)
Or Even
As you can see, ASP.Net has successfully request arguments, however, it doesn't return it's in raw manner. Stream has position=0, contentConsumed=false, and everything else is just fine. But i can't read data passed to the controller.
What's wrong here?
ASP.NET Web API reads the content only once, so at the time that you access the content stream, it has already been read and the stream is positioned at its end. Another attempt to read the content will return nothing.
However, in a small sample I was able to reset the stream and read it again:
private async Task<string> GetDataAsString(HttpContent content)
{
if (content == null)
return null;
using (var str = await content.ReadAsStreamAsync())
{
if (str.CanSeek)
str.Seek(0, System.IO.SeekOrigin.Begin);
using (var rdr = new StreamReader(str))
{
return rdr.ReadToEnd();
}
}
}
However, in order to avoid side effects, you might want to consider to use the ActionArguments property of the ActionContext. You can use this property to retrieve the values that are handed to the action for logging. This does not interfere with the internal plumbing of ASP.NET Web API.
I have a simple Action on a controller which returns a PDF.
Works fine.
public FileResult GetReport(string id)
{
byte[] fileBytes = _manager.GetReport(id);
string fileName = id+ ".pdf";
return File(fileBytes, MediaTypeNames.Application.Octet, fileName);
}
When the manager fails to get the report I get back null or an empty byte[].
How can I communicate to the browser that there was a problem, when the result is set to a FileResult?
I would change the return type of your method to ActionResult.
public ActionResult GetReport(string id)
{
byte[] fileBytes = _manager.GetReport(id);
if (fileBytes != null && fileBytes.Any()){
string fileName = id+ ".pdf";
return File(fileBytes, MediaTypeNames.Application.Octet, fileName);
}
else {
//do whatever you want here
return RedirectToAction("GetReportError");
}
}
The FileResult class inherits from ActionResult. So, you can define your Action like this:
public ActionResult GetReport(string id)
{
byte[] fileBytes = _manager.GetReport(id);
string fileName = id + ".pdf";
if(fileBytes == null || fileBytes.Length == 0)
return View("Error");
return File(fileBytes, MediaTypeNames.Application.Octet, fileName);
}
If you want to "communicate to the browser" that there was an error, the standard "HTTP way" is to return status code 500, especially if your request is invoked using Ajax, so that you can gracefully handle the exception.
I would suggest to simply throw an Exception when no report found for the provided id:
public FileResult GetReport(string id)
{
// could internally throw the Exception inside 'GetReport' method
byte[] fileBytes = _manager.GetReport(id);
// or...
if (fileBytes == null || !fileBytes.Any())
throw new Exception(String.Format("No report found with id {0}", id));
return File(fileBytes, MediaTypeNames.Application.Octet, fileName = id+ ".pdf");
}
Explicitly redirecting to an error page or returning a ViewResult is not the best approach in ASP.NET MVC as this is usually the role of the HandleError filter (which is applied by default) that can be easily configured to either redirect or render some View with the Exception details (while still maintaining HTTP status 500).
This is all true assuming that a failure to fetch a report is indeed considered an exception. If it's not (for example, if we expect some report to not have an available file to dump), explicitly returning a Redirect/View result is totally acceptable.
Another workaround for handling prerequisites is to split download process into two stages. First is to check preconditions in server side method which is executed as ajax/post method.
Then if these preconditions are fulfilled you can start download request (e.g. in onSuccess callback where it is checked the return value indicating fulfillment) in which (on server side) you will handle potential exceptions in a way as it was described in above posts.