Can you override the body deserialization web api - c#

When I attempt to override serialization settings for a controller they are used for creating the response but ignored when parsing the body.
I need to override the json serialization settings for each requests in WebAPI. I am trying to change the serialization settings based on the route in use. Example: if it is a V1 api use SerializationSettingsA, if it is a V2 api use SerializationSettingsB.
I have tried multiple approaches including overriding the IContentNegotiator and IHttpControllerActivator hoping to set the serialization settings for the context but in all case it does not work. The behavior I am seeing is that the override serialization settings are used when crating the response body but not when parsing the request. Is there some other settings that need overriding for changing how the request body is parsed.
class PerControllerConfigActivator : IHttpControllerActivator
{
private static readonly DefaultHttpControllerActivator Default = new DefaultHttpControllerActivator();
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
controllerDescriptor.Configuration = HttpConfigurationFactory.CreateDefaultConfiguration();
controllerDescriptor.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters.Insert(0,
new VersionedPropertyConverter(request.RequestUri.AbsoluteUri));
var result = Default.Create(request, controllerDescriptor, controllerType);
return result;
}
}
public class Startup
{
public void Configuration(IAppBuilder app)
{
var configuration = new HttpConfiguration();
configuration.MapHttpAttributeRoutes();
configuration.Formatters.Clear();
configuration.Services.Replace(typeof(IHttpControllerActivator), new PerControllerConfigActivator());
app.UseWebApi(configuration);
}
}

Borrowing some ideas from here, you could use custom input formatters with a specific content-type header on the request itself.
First you need to create an input formatter for the overrided version
public class MyNonDefaultJsonFormatter : InputFormatter
{
public MyNonDefaultJsonFormatter()
{
this.SupportedMediaTypes.Clear()
this.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/non-default-json");
}
public override bool CanRead(InputFormatterContext context)
{
return base.CanRead(context);
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext)
{
//Do some custom deserializing on context.HttpContext.Request.Body
}
}
Then you need to register the input formatter in configure services
services.AddMvc(options =>
{
options.InputFormatters.Add(new MyNonDefaultJsonFormatter());
}
Then specify the Content-Type as "application/non-default-json" when making your request.
As another idea, you may be able to remove the default JSON deserializer all together and instead register two serializers of your own, both accepting standard application/json, but with type checking in the CanReadType override that allows you to differentiate. I haven''t had a chance to test this out myself, but if I do will report back here.

Related

Throwing custom exceptions on JSON type mismatches ASP.NET Core

What I am attempting to do is throw my own exceptions when there is a type mismatch between the JsonTokenType and the object/struct I am converting to.
For example, my object is LoginRequest:
public class LoginRequest
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
}
And my controller:
[HttpPost]
public async Task<IActionResult> CreateCredentialsAsync([FromBody] LoginRequest request)
{
// Do Stuff
}
But if a user provides an integer instead of a string (or really any type mismatch) for the username/password I want to serve a custom exception.
For example imagine the client calls my server with the following JSON body:
POST {ip}/api/login
content-type: application/json
{
"username": 123,
"password": "password"
}
now from an IAsyncActionFilter I can read the ModelState and see it is invalid, but I don't see a way to differentiate error causes and throw different exceptions.
What I would like to do is throw a CustomBadRequestException(errorCode: 3, message: "Really, you think that should be a number and not a string"), but if they fail to provide the username at all I want to throw DifferentCustomBadRequestException(errorCode: 2, message: "Nice try hacker")
Do I need a custom model binder in order to do this (or even extend an existing model binder), or do I need some sort of deserialization setting and/or converter that can provide more specific exceptions based on what went wrong, or both?
Bonus question: Is it possible to collect all the errors in the model state before the action filter is called (this sound like it would absolutely require a custom model binder but I figured I would ask)?
I'm not sure I'm fully across your problem statement, but assuming you want more control over model deserialisation, some tweaks can be made to MVC json serialiser options:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(o => { })
.AddJsonOptions(s =>
{
s.SerializerSettings.Converters.Add(new Converter()); // one way to gain more control will be to use custom converter for your type, see implementation down below
//if you are after something a bit more simple, setting behaviours and handling general error events might work too
s.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error; // you probably want that so your missing
s.SerializerSettings.Error = delegate(object sender, ErrorEventArgs args)
{
// throw your custom exceptions here
var message = args.ErrorContext.Error.Message;
args.ErrorContext.Handled = false;
};
});
}
implementing Converter is fairly easy:
class Converter : JsonConverter<LoginRequest>
{
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, LoginRequest value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override LoginRequest ReadJson(JsonReader reader, Type objectType, LoginRequest existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// your logic here
}
}
UPD after getting a better view of your specific requirement re handling primitive types it seems trying to fiddle with MVC serialiser gets a bit too cumbersome. Reason being, the level of control you're after (especially checking primitive types ) seems to be available on JsonTextReader level, but it seems overriding that would mean reimplementing a significant chunk of library code:
services.AddMvc(o =>
{
o.InputFormatters.RemoveType<JsonInputFormatter>();
o.InputFormatters.Add(new MyJsonInputFormatter(logger, serializerSettings, charPool, objectPoolProvider));// there are quite a few parameters that you need to source from somewhere before instantiating your formatter.
})
....
class MyJsonInputFormatter: JsonInputFormatter {
public override async Task<InputFormatterResult> ReadRequestBodyAsync(
InputFormatterContext context,
Encoding encoding)
{
...your whole implementation here...
}
}
Therefore I think the most viable approach would be injecting custom middleware before MVC and doing something along the lines of schema validation for your raw json. Since MVC will need to re-read your json again (for model binding etc), you would want to check out my other answer that caters for request steam rewinding.

Attribute to make a given action to accept a given content-type?

Is it possible in ASP.NET Core MVC to change only certain actions to accept plain/text or application/xml (ie. content-type) with an attribute without changing the default input formatters?
Out of the box, ASP.NET Core only supports JSON or XML. As long as you set the content types of the payload, it should deserialize correctly regardless of the controller action.
If you want support for any other content type (e.g. text/plain) you can create a custom formatter
Example taken directly from aspnet samples repo:
public class TextPlainInputFormatter : TextInputFormatter
{
public TextPlainInputFormatter()
{
SupportedMediaTypes.Add("text/plain");
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
SupportedEncodings.Add(UTF16EncodingLittleEndian);
}
protected override bool CanReadType(Type type)
{
return type == typeof(string);
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
string data = null;
using (var streamReader = context.ReaderFactory(context.HttpContext.Request.Body, encoding))
{
data = await streamReader.ReadToEndAsync();
}
return InputFormatterResult.Success(data);
}
}

Custom MediaTypeFormatter (Serializer) for ApiController specific method

I have ApiController in place and one of methods needs have custom serialization.
public async Task<Session> PostSession([FromBody] Session session)
There is massive Session object that holds everything and consumer does not want do get more than half of it, normally I could just decorate properties with [XmlIgnore()] and be done, however this object is being passed internally across many APIs and removing these would break these mechanisms.
Another solution would be to just replace that object with different one like CastratedSession and return that. I can't do this since same endpoint is being called by existing 3rd party APIs that expect to get Session or they would have to rewrite their stuff (not going to happen).
I then proposed to create a different endpoint that this 3rd party could call - but architect objected and said to do the custom MediaTypeFormatter based on specific content-type value they will set in header, problem is we already are using in this same controller custom MediaTypeFormatter, and the way I understand out of the box one can only set them in configuration per controller
public static void ConfigureApis(HttpConfiguration config)
{
config.Formatters.Add(new CustomJsonFormatter());
config.Formatters.Add(new CustomXmlFormatter());
}
Which sorta paints me into the corner.
How (Can) I setup a custom MediaTypeFormatter per method on ApiController?
you could write a custom action filter, override the OnActionExecuting method to inspect the target action, then add the proper formatter to the configuration.
internal class DecisionMakingFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var actionName= actionContext.ActionDescriptor.ActionName;
if(actionName == "Some Foo")
{
actionContext.RequestContext.Configuration.Formatters.Add(new CustomMediaFormatter());
}
base.OnActionExecuting(actionContext);
actionContext.RequestContext.Configuration.Formatters.Remove(new CustomMediaFormatter());
}
}
figured out a workaround myself
I inherited and replaced CustomXmlFormatter() with EvenMoreCustomXmlFormatter()
then in WriteToStreamAsync did this:
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
if (content.Headers.ContentType.MediaType == "text/sessionXml") // this was agreed with 3rd party
{
//do my serialization stuff
}
else
{
return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
}
}
hope this saves you some time.

Intercept webapi json formatting errors

I'd like to have a way to intercept the exception that occurs when you send in malformed json to a webapi endpoint, so that I can return a semantic error code as opposed to just 500. (e.g. "Fix your broken JSON or go to hell")
You can create your custom validation filter attribute by deriving from ActionFilterAttribute:
public class ValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext
.Request
.CreateErrorResponse(HttpStatusCode.BadRequest,
actionContext.ModelState);
}
}
}
Now, you may either decorate your actions with it:
[HttpGet]
[ValidationFilter()]
public string DoSomethingCool()
or register it globally via your config:
config.Filters.Add(new ValidationFilterAttribute());

Dependency injection for ASP.NET Web API action method parameters

I am working on an ASP.NET Web API project in C# for a JSON interface to a mobile application. My idea was to create interfaces for all requests and then only use these interfaces in the Web API code.
I ended up with something like this:
public interface IApiObject {}
public interface IApiResponse<T> : IApiObject where T : IApiObject {}
public interface IApiRegistrationRequest : IApiObject {}
My controller looks like this:
public class MyApiController : ApiController
{
public IApiResponse<IApiObject> Register(IApiRegistrationRequest request) {
// do some stuff
}
}
My Web API project also contains implementations of these interfaces.
I assumed Web API projects use model binding like MVC projects do, so I created an inheritance aware ModelBinderProvider for providing a binder for all IApiObjects and a custom model binder using a Unity container to resolve the interfaces to their implementations.
However, after some more investigation, I came across How Web API does parameter binding and found out that Web API uses formatters instead of model binders for complex types. The linked blog post recommends using a ModelBinderAttribute on my action parameters, but that attribute only accepts a type as a parameter. My custom model binder does, however, not contain an empty constructor (it needs a unity container), so I would need to pass an instance of it.
The other way I can think of is using dependency injection for the formatters. Unfortunately, I am unfamiliar with them as I have never used them before.
Which is the right way to go? And how do I do it?
This is what I came up with now and it works.
I decided to create a custom formatter which does the unity calls and forwards all further operations to another formatter using the resolved type. It looks like a lot of code, but that is only because all the methods need to be overwritten so the type can always be resolved.
public class UnityFormatter : MediaTypeFormatter
{
private MediaTypeFormatter formatter;
private IUnityContainer container;
public UnityFormatter(MediaTypeFormatter formatter, IUnityContainer container)
{
this.formatter = formatter;
this.container = container;
foreach (var supportedMediaType in this.formatter.SupportedMediaTypes)
{
this.SupportedMediaTypes.Add(supportedMediaType);
}
foreach (var supportedEncoding in this.formatter.SupportedEncodings)
{
this.SupportedEncodings.Add(supportedEncoding);
}
foreach (var mediaTypeMapping in this.MediaTypeMappings)
{
this.MediaTypeMappings.Add(mediaTypeMapping);
}
this.RequiredMemberSelector = this.formatter.RequiredMemberSelector;
}
private Type ResolveType(Type type)
{
return this.container.Registrations.Where(n => n.RegisteredType == type).Select(n => n.MappedToType).FirstOrDefault() ?? type;
}
public override bool CanReadType(Type type)
{
return this.formatter.CanReadType(this.ResolveType(type));
}
public override bool CanWriteType(Type type)
{
return this.formatter.CanWriteType(this.ResolveType(type));
}
public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
{
return this.formatter.GetPerRequestFormatterInstance(this.ResolveType(type), request, mediaType);
}
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
return this.formatter.ReadFromStreamAsync(this.ResolveType(type), readStream, content, formatterLogger);
}
public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
{
this.formatter.SetDefaultContentHeaders(this.ResolveType(type), headers, mediaType);
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
return this.formatter.WriteToStreamAsync(this.ResolveType(type), value, writeStream, content, transportContext);
}
}
Finally, register our custom formatter in the application config (Global.asax Application_Start). I chose to replace all current formatters with an instance of my custom one, so I get reflection for all data types.
// set up unity container, register all types
UnityContainer container = new UnityContainer();
container.RegisterType<IApiRegistrationRequest, ApiRegistrationRequest>();
// save existing formatters and remove them from the config
List<MediaTypeFormatter> formatters = new List<MediaTypeFormatter>(GlobalConfiguration.Configuration.Formatters);
GlobalConfiguration.Configuration.Formatters.Clear();
// create an instance of our custom formatter for each existing formatter
foreach (MediaTypeFormatter formatter in formatters)
{
GlobalConfiguration.Configuration.Formatters.Add(new UnityFormatter(formatter, container));
}
I Suggest you Have a look at Service Stack
http://www.servicestack.net/
Its got the same all around design as asp.net-WebApi but it has goodies like IOC baked into it.
There is a amazing series on service stack at
http://pluralsight.com/training/Courses/TableOfContents/service-stack

Categories

Resources