I'm trying to understand why my JSON Object when casted to an specficied object is giving me NULL when one of the attributes sent im my json, which is a number, is larger than Int32. My whole object is not binding and not not throwing up any error, just becoming null.
Now my situation. I started a new project just to understand it, and I all the question over here I could found.
I do have this model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace TestJson.Models
{
public class Credential
{
public int IdSource { get; set; }
public DateTime TransactionDate { get; set; }
}
}
I have this Controller:
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using TestJson.Models;
namespace TestJson.Controllers
{
[RoutePrefix("PreAuth")]
public class PreAuthController : ApiController
{
[HttpPost]
[ActionName("PostObject")]
public async Task<IHttpActionResult> PostObject([FromBody]Credential input)
{
return await Task.FromResult(Ok(input));
}
[HttpPost]
[ActionName("PostJObject")]
public async Task<IHttpActionResult> PostJObject([FromBody]JObject input)
{
Credential Credential = input.ToObject<Credential>();
return await Task.FromResult(Ok(Credential));
}
}
}
And I have this JSON object:
{
"IdSource": 11111111111111,
"TransactionDate": "2017-11-24T11:59:01.7567643-02:00"
}
So, when I calling api/PreAuth/PostObject within the above JSON, I just get null as parameter casted to my object.
But if I call the same JSON /api/PreAuth/PostJObject, I can receive the given error below:
{
"Message": "An error has occurred.",
"ExceptionMessage": "Value was either too large or too small for an Int32.",
"ExceptionType": "System.OverflowException",
"StackTrace": " em System.Convert.ToInt32(Int64 value)..."
}
So, If I change my JSON object modifying my IdSource attribute to a shorter number, both of my methods works fine because it can fit to int limits.
My point is; Why when I call the method PostObject expecting a specified object in parameter and it couldn't cast my JSON into it properly it is giving me null and not filling my others properties?
The best scenario would be at least see some error, but I just get nothing at all. All my object is swallowed and it becomes like an stealth error and the caller don't know exactly which attribute he is giving wrong.
How can I workaround this using object as parameter instead JObject?
You should configure your JsonFormatter on your HttpConfiguration (application Startup).
An example:
protected virtual void ConfigureFormatter(HttpConfiguration config)
{
JsonConvert.DefaultSettings = () =>
{
JsonSerializerSettings result = new JsonSerializerSettings();
//Include the type name to be able to deserialize into the derived instead of the base type.
result.TypeNameHandling = TypeNameHandling.Auto;
//Do not include null properties in serialized JSON.
result.NullValueHandling = NullValueHandling.Ignore;
return result;
};
JsonMediaTypeFormatter jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings = JsonConvert.DefaultSettings();
config.Formatters.Clear();
config.Formatters.Add(jsonFormatter);
config.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator(jsonFormatter));
}
EDIT
The previous answer works for extended objects (I thought your were having null values on that).
You can however add a custom filter for filtering valid models like explained here:
Capture exception during request deserialization in WebAPI C#
Related
I'm trying to implement a new web API. This API returns a JSON from HTTP-request.
So far I wrote very basic code, but the strange thing is that I get an error using XML template - and I have no idea what to do:
This is the call: http://localhost:55643/api/ShipmentsStatus/getShipmentsStatusJSON
The code is here:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace RunCom.WebAPI.Controllers
{
[Route("api/[controller]")]
public class ShipmentsStatusController : ApiController
{
// /api/ShipmentsStatus/getShipmentsStatusJSON
public ShipmentsStatusController()
{
int i = 0;
}
[HttpGet]
[Route("getShipmentsStatusJSON")]
public IEnumerable<String> Get()
{
test check = new test("1");
yield return JsonConvert.SerializeObject(check);
}
}
internal class test
{
string key;
public test(string k)
{
key = k;
}
}
}
The error I got is here:
<Error>
<Message>No HTTP resource was found that matches the request URI
'http://localhost:55643/api/ShipmentsStatus/getShipmentsJSON'.</Message>
<MessageDetail>No action was found on the controller 'ShipmentsStatus' that matches the request.</MessageDetail>
</Error>
What is wrong with my code?
Try to fix the route
[Route("~/api/ShipmentsStatus/GetShipmentsStatusJSON")]
public IEnumerable<string> Get()
{
return new List<string> {"1","2","3"};
}
You should use http://localhost:55643/api/ShipmentsStatus/getShipmentsStatusJSON or change [Route("getShipmentsStatusJSON")] to the appropriate API method name
I am a frontend developer so forgive my lack of ability to explain my issue.
I am trying to create some pages in an Umbraco project that display data using Vue.js. For this, I am trying to set up a custom API controller that will return the data I want, when called.
A simple example would be that I want to return all blog articles. Below is the code I have currently got:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Web;
using System.Web.Http;
using Umbraco.Web.WebApi;
using Umbraco.Web.PublishedContentModels;
using Newtonsoft.Json;
namespace Controllers.WebAPI.Qwerty
{
[Route("api/[controller]")]
public class PostsApiController : UmbracoApiController
{
[HttpGet]
public string Test()
{
return "qwerty";
}
}
}
I've read numerous articles and just can't seem to grasp what I need to do to query Umbraco for the data I want back?
I've tried adding
var content = Umbraco.TypedContent(1122);
And then returning that but I get errors stating:
(local variable) Umbraco.Core.Models.IPublishedContent content
Cannot implicitly convert type 'Umbraco.Core.Models.IPublishedContent' to 'string'
I have then tried serialising the var content but I get stuck with:
Self referencing loop detected for property 'FooterCtalink' with type
'Umbraco.Web.PublishedContentModels.Blog'. Path
'ContentSet[0].FeaturedProducts[0].Features[0].ContentSet[0]'.
Any help would be fantastic!
EDIT:
I have no edited the controller to be like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Web;
using Umbraco.Web.WebApi;
using Umbraco.Web.PublishedContentModels;
using Newtonsoft.Json;
using System.Web.Mvc;
using DTOs.PostDTO;
namespace Controllers.WebAPI.Qwerty
{
[Route("api/[controller]")]
public class PostsApiController : UmbracoApiController
{
[HttpGet]
public PostDTO Test()
{
// 1. Get content from umbraco
var content = Umbraco.TypedContent(1122);
// 2. Create instance of your own DTO
var myDTO = new PostDTO();
// 3. Pupulate your DTO
myDTO.Url = content.Url;
// 4. return it
return myDTO;
}
}
}
And created a DTO like so:
namespace DTOs.PostDTO
{
public class PostDTO
{
public string Url { get; set; }
}
}
However, when console logging my data after the ajax request, I only only getting 1122.
The issue is that you can't return a .NET Object in JSON that has the circular dependency.
To solve your problem, you can simply follow the below steps:
Create your own DTO & add required properties in that.
Fetch content from Umbraco API in C# & populate your custom DTO object.
Return that DTO from JsonResult.
Your code will look like below:
[Route("api/[controller]")]
public class PostsApiController : UmbracoApiController
{
[HttpGet]
public MyDTO Test()
{
// 1. Get content from umbraco
var content = Umbraco.TypedContent(1122);
// 2. Create instance of your own DTO
var myDTO = new MyDTO();
// 3. Pupulate your DTO
myDTO.SomeProperty = content.SomeProperty;
// 4. return it
return myDTO;
}
}
You are on the right track.
I think you need to return ActionResult instead of string.
Something like:
[HttpGet]
public ActionResult Test()
{
var content = Umbraco.TypedContent(1122);
return new JsonResult(content);
}
This should return the umbraco object as Json.
I'm trying to emulate the behavior of the [ApiController] attribute for model validation, however I'd like to return a JSON object I've made with the validation errors in an Error array within the JSON.
The challenge I'm facing is that I'm unsure how to access validation errors from within the Attribute and I'd like to use the attribute at the class level, so it will run on all controller methods without need of supplying the attribute for each action.
Any direction would be much appreciated.
edit: linked duplicate is how to create custom attribute. I'm looking how to access model validation errors from within an attribute.
I was able to figure out my issue. I was able to utilize ModelState.IsValid in an OnActionExecuting method to access the errors. Unfortunately I'm not familiar enough with making a class level attribute so I have to apply this to all post/patch methods in order for it to work. If someone else comes up with a way to do that easily, let me know!
Project.Structure is for formatting JSON for those curious.
using System;
using System.Collections.Generic;
using System.Linq;
using Project.Structure;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Project.Attributes
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errorList = new List<string>();
foreach (var modelError in context.ModelState.Values)
{
errorList.AddRange(modelError.Errors.Select(error => error.ErrorMessage));
}
var response = new ResponseDto<object>
{
Success = false,
TransactionId = Guid.NewGuid().ToString(),
ResponseType = ResponseType.Operation.Description(),
Response = null,
Errors = errorList,
Warnings = null
};
context.Result = new BadRequestObjectResult(response);
}
}
}
}
Long story short - I have an Entity Framework model which accepts Enum type property:
public class FileUploads {
public AllowedFileTypes FileType { get; set; }
}
Enum is:
public enum AllowedFileTypes {
jpg,
png,
gif,
jpeg,
bmp,
}
Then, in a Web API controller I set a validation attribute for the IFormFile like this:
[HttpPost]
public async Task<IActionResult> Upload(
[Required]
[FileExtensions(Extensions = "jpg,png,gif,jpeg,bmp")] // allowed filetypes
IFormFile file)
{
return Ok()
}
The method is used to upload files. Now, the problem is that I am basically setting FileExtensions attribute allowed formats manually. This means that whenever a new file format is added to enum in the future - I will need to go and update each FileExtensions attribute manually. This could be easily forgotten, or any other developer could not be aware of this fact..
So, I was thinking whether or not or How is it possible to pass Enum type parameter to the FileExtensions attribute?
My attempt was the following:
[FileExtensions(Extensions = string.Join(",", Enum.GetValues(typeof(FileExtensions))))]
Unfortunately, Extensions parameter must be a const type string, therefore an error is thrown by VS. I can of course write my own custom validation attribute such as this:
FileExtensions fileExtension;
bool fileExtensionParseResult = Enum.TryParse<FileExtensions>(Path.GetExtension(file.FileName), true, out fileExtension);
Any other ideas?
So, when I deal with white lists, I generally utilize a configuration file instead of hard coding this into the application. Also, I would utilize the Content-Type header to determine the content type of the request. They should send something like image/jpeg when uploading a jpg.
If this doesn't give you enough information to get started, please comment, and I will work up a quick example.
Edited:
Here is an example from my own project. In appsettings.json, add the below:
"AllowedFileUploadTypes": {
"image/jpeg": "jpg",
"video/quicktime": "mov"
}
I generally create a wrapper class for accessing settings, and below is an example of mine my .NET Core version:
using System.Linq;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
public class AppConfigurationManager
{
private IConfiguration _configuration;
public AppConfigurationManager(IConfiguration configuration)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
public IDictionary<string, string> AllowedFileUploadTypes =>
_configuration.GetSection(nameof(AllowedFileUploadTypes)).GetChildren()
.Select(item => new KeyValuePair<string, string>(item.Key, item.Value))
.ToDictionary(x => x.Key, x => x.Value);
}
Of course you have to register this in Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//stuff...
services.AddSingleton(Configuration);
services.AddSingleton<AppConfigurationManager>();
//other stuff...
}
}
Then you can use the AppConfigurationManager.AllowedFileUploadTypes to evaluate the IFormFile.ContentType property to validate the content type of the file is valid. You can attempt to get the value from the dictionary and then validate against that property. Based on the documentation, I am assuming that the ContentType property will be populated by the Content-Type header. I generally upload files using chunks, so I have not used IFormFile.
Edited: Wanting a way to apply to the action.
Using an ActionFilterAttribute, you could do something like this:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
public class ValidateFileExtensionsAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var fileKeyValue = context.ActionArguments.FirstOrDefault(x => typeof(IFormFile).IsAssignableFrom(x.Value.GetType()));
if (fileKeyValue.Value != null)
{
AppConfigurationManager sessionService = context.HttpContext.RequestServices.GetService(typeof(AppConfigurationManager)) as AppConfigurationManager;
IFormFile fileArg = fileKeyValue.Value as IFormFile;
if (!sessionService.AllowedFileUploadTypes.Keys.Any(x => x == fileArg.ContentType))
{
context.Result = new ObjectResult(new { Error = $"The content-type '{fileArg.ContentType}' is not valid." }) { StatusCode = 400 };
//or you could set the modelstate
//context.ModelState.AddModelError(fileKeyValue.Key, $"The content-type '{fileArg.ContentType}' is not valid.");
return;
}
}
await next();
}
}
Then you could apply that to the action like this:
[HttpPost]
[ValidateFileExtensions]
public async Task<IActionResult> Upload([Required]IFormFile file)
{
return Ok();
}
You could modify the ActionFilter to set the ModelState or you could just return the value.
For some reason, Request.CreateResponse is now "red" in VS2012 and when I hover over the usage the IDE says
Cannot resolve symbol 'CreateResponse'
Here is the ApiController Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Filters;
using GOCApi.Attributes;
using GOCApi.Models;
using GOCApi.Models.Abstract;
using AttributeRouting;
using AttributeRouting.Web.Http;
namespace GOCApi.Controllers
{
[RoutePrefix("Courses")]
public class CoursesController : ApiController
{
private ICoursesRepository _coursesRepository { get; set; }
public CoursesController(ICoursesRepository coursesRepository)
{
_coursesRepository = coursesRepository;
}
[GET("{id}")]
public HttpResponseMessage Get(int id)
{
var course = _coursesRepository.GetSingle(id);
if (course == null)
return Request.CreateResponse(HttpStatusCode.NotFound, "Invalid ID");
return Request.CreateResponse(HttpStatusCode.OK, course);
}
}
}
Granted, the code does compile and works, it's just really annoying seeing all the "red" on my screen. Also, the intellisense doesn't work now when I type Request.CreateResponse. This also used to work, but I started developing other parts to my API and just came back to building controllers so I do not know what happened.
Any thoughts?
You need to add a reference to System.Net.Http.Formatting.dll. The CreateResponse extension method is defined there.
Because this extension method lives in System.Net.Http, you just need to include it in your usings statements.
using System.Net.Http;
You can use
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.Gone)
instead of
Request.CreateResponse(HttpStatusCode.Gone)
This is because you have to do:
namespace GOCApi.Controllers
{
[RoutePrefix("Courses")]
public class CoursesController : ApiController
{
private ICoursesRepository _coursesRepository { get; set; }
public CoursesController(ICoursesRepository coursesRepository)
{
_coursesRepository = coursesRepository;
}
[GET("{id}")]
public HttpResponseMessage Get(int id)
{
var course = _coursesRepository.GetSingle(id);
if (course == null){
return this.Request.CreateResponse(HttpStatusCode.NotFound, "Invalid ID");
}
return this.Request.CreateResponse(HttpStatusCode.OK, course);
}
}
}
Note the this.
In my case the compiler now gets it.
Saw the example here
Add a reference to System.Web.Http to the project. Then add
Using System.Web
to the top of the cs file.
This a known issue with VB.NET and Request.CreateResponse.
There is a workaround:
Missing request.CreateResponse in vb.net Webapi Projects