I have a custom OutputFormatter in my .net core project. In there I want to use some info inside the querystring of the initial request.
Inside the controller this is nicely done with the FromQuery modelbinder, giving me an object to work with. I would like to have this object (model) in my output formatter as well.
Can I somehow call FromQuery as an instance or such, so I can pass in the HttpContext or the querystring even, to get the model?
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
{
// Want a model from my querystring here
}
Use HttpContext.Items. The objects placed in there will be cleaned up at the end of request. And you can pass even more with it, defaulted values or changed values (probably it can be a dangerous point if you are going to change binded object)
// GET api/values
[HttpGet]
public ActionResult Get([FromQuery] QData data)
{
HttpContext.Items["data"] = data;
.......
return Ok(....);
}
Also you can have multiple formatters for different types of requests.
public class Formatter : OutputFormatter
{
public override bool CanWriteResult(OutputFormatterCanWriteContext context)
{
return context.HttpContext.Items["data"] is QData;
}
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
{
var incoming = context.HttpContext.Items["data"] as QData;
.......
}
}
The same way you can put any other object in Items and work with it in formatter. In case of other object it can be more generic and stable solution as it relies on specific structure.
Related
I have a controller action:
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null) { }
I need to be able to call this endpoint either through a razor page or using the HttpClient's PostAsJsonAsync.
I got this working for the razor page. But when I do:
var response = await client.PostAsJsonAsync($"{api}Account/Register", new { email, password });
I get a 415 Unsupported Media Type.
How can I support both ways of submitting the data?
The new behavior of model binding designed in asp.net core is a bit inconvenient in some cases (for some developers) but I personally think it's necessary to reduce some complexity in the binding code (which can improve the performance a bit). The new behavior of model binding reflects clearly the purpose of the endpoints (MVC vs Web API). So usually your web browsers don't consume Web API directly (in case you use it, you must follow its rule, e.g: post the request body with application/json content-type).
If you look into the source code of asp.net core, you may have the best work-around or solution to customize it to support the multi-source model binding again. I did not look into any source code but I've come up with a simple solution to support model binding from either form or request body like this. Note that it will bind data from either the form or request body, not combined from both.
The idea is just based on implementing a custom IModelBinder and IModelBinderProvider. You can wrap the default one (following the decorator pattern) and add custom logic around. Otherwise you need to re-implement the whole logic.
Here is the code
//the custom IModelBinder, this supports binding data from either form or request body
public class FormBodyModelBinder : IModelBinder
{
readonly IModelBinder _overriddenModelBinder;
public FormBodyModelBinder(IModelBinder overriddenModelBinder)
{
_overriddenModelBinder = overriddenModelBinder;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var httpContext = bindingContext.HttpContext;
var contentType = httpContext.Request.ContentType;
var hasFormData = httpContext.Request.HasFormContentType;
var hasJsonData = contentType?.Contains("application/json") ?? false;
var bindFromBothBodyAndForm = bindingContext.ModelMetadata is DefaultModelMetadata mmd &&
(mmd.Attributes.Attributes?.Any(e => e is FromBodyAttribute) ?? false) &&
(mmd.Attributes.Attributes?.Any(e => e is FromFormAttribute) ?? false);
if (hasFormData || !hasJsonData || !bindFromBothBodyAndForm)
{
await _overriddenModelBinder.BindModelAsync(bindingContext);
}
else //try binding model from the request body (deserialize)
{
try
{
//save the request body in HttpContext.Items to support binding multiple times
//for multiple arguments
const string BODY_KEY = "___request_body";
if (!httpContext.Items.TryGetValue(BODY_KEY, out var body) || !(body is string jsonPayload))
{
using (var streamReader = new StreamReader(httpContext.Request.Body))
{
jsonPayload = await streamReader.ReadToEndAsync();
}
httpContext.Items[BODY_KEY] = jsonPayload;
}
bindingContext.Result = ModelBindingResult.Success(JsonSerializer.Deserialize(jsonPayload, bindingContext.ModelType));
}
catch
{
bindingContext.Result = ModelBindingResult.Success(Activator.CreateInstance(bindingContext.ModelType));
}
}
}
}
//the corresponding model binder provider
public class FormBodyModelBinderProvider : IModelBinderProvider
{
readonly IModelBinderProvider _overriddenModelBinderProvider;
public FormBodyModelBinderProvider(IModelBinderProvider overriddenModelBinderProvider)
{
_overriddenModelBinderProvider = overriddenModelBinderProvider;
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
var modelBinder = _overriddenModelBinderProvider.GetBinder(context);
if (modelBinder == null) return null;
return new FormBodyModelBinder(modelBinder);
}
}
We write a simple extension method on MvcOptions to help configure it to override an IModelBinderProvider with the custom one to support multisource model binding.
public static class MvcOptionsExtensions
{
public static void UseFormBodyModelBinderProviderInsteadOf<T>(this MvcOptions mvcOptions) where T : IModelBinderProvider
{
var replacedModelBinderProvider = mvcOptions.ModelBinderProviders.OfType<T>().FirstOrDefault();
if (replacedModelBinderProvider != null)
{
var customProvider = new FormBodyModelBinderProvider(replacedModelBinderProvider);
mvcOptions.ModelBinderProviders.Remove(replacedModelBinderProvider);
mvcOptions.ModelBinderProviders.Add(customProvider);
}
}
}
To support binding complex model from either form or request body, we can override the ComplexTypeModelBinderProvider like this:
//in the ConfigureServices method
services.AddMvc(o => {
o.UseFormBodyModelBinderProviderInsteadOf<ComplexTypeModelBinderProvider>();
};
That should suit most of the cases in which your action's argument is of a complex type.
Note that the code in the FormBodyModelBinder requires the action arguments to be decorated with both FromFormAttribute and FromBodyAttribute. You can read the code and see where they're fit in. So you can write your own attribute to use instead. I prefer to using existing classes. However in this case, there is an important note about the order of FromFormAttribute and FromBodyAttribute. The FromFormAttribute should be placed before FromBodyAttribute. Per my test, looks like the ASP.NET Core model binding takes the first attribute as effective (seemingly ignores the others), so if FromBodyAttribute is placed first, it will take effect and may prevent all the model binders (including our custom one) from running when the content-type is not supported (returning 415 response).
The final note about this solution, it's not perfect. Once you accept to bind model from multi-sources like this, it will not handle the case of not supporting media type nicely as when using FromBodyAttribute explicitly. Because when we support multi-source model binding, the FromBodyAttribute is not used and the default check is not kicked in. We must implement that ourselves. Because of multiple binders joining in the binding process, we cannot easily decide when the request becomes not supported (so even you add a check in the FormBodyModelBinder, you can successfully set the response's status code to 415 but it may not be right and the request is still being processed in the selected action method). With this note, in case of media type not supported, you should ensure that your code in the action method is not broken by handling empty (or null) model argument.
Here is how you use the custom model binder:
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Register([FromForm] [FromBody] RegisterViewModel model, string returnUrl = null) { }
I'm executing the URL
https://localhost:44310/api/Licensee/{"name":"stan"}
in address field of my browser, getting the error
"title": "Unsupported Media Type", "status": 415
which is described as
... the origin server is refusing to service the request because the payload
is in a format not supported by this method on the target resource.
The suggested troubleshot is
... due to the request's indicated Content-Type or Content-Encoding, or as a result of inspecting the data ...
I can't really control what header the browser provides. Due to intended usage, I can't rely on Postman or a web application. It needs to be exected from the URL line. The parameter will differ in structure, depending what search criteria that are applied.
The controller looks like this.
[HttpGet("{parameters}")]
public async Task<ActionResult> GetLicensee(LicenseeParameters parameters)
{
return Ok(await Licensee.GetLicenseeByParameters(parameters));
}
I considered decorating the controller with [Consumes("application/json")] but found something dicouraging it. I tried to add JSON converter as suggested here and here but couldn't really work out what option to set, fumbling according to this, not sure if I'm barking up the right tree to begin with.
services.AddControllers()
.AddJsonOptions(_ =>
{
_.JsonSerializerOptions.AllowTrailingCommas = true;
_.JsonSerializerOptions.PropertyNamingPolicy = null;
_.JsonSerializerOptions.DictionaryKeyPolicy = null;
_.JsonSerializerOptions.PropertyNameCaseInsensitive = false;
});
My backup option is to use query string specifying the desired options for a particular search. However, I'd prefer to use the object with parameters for now.
How can I resolve this (or at least troubleshoot further)?
The reason is that there might be a loooot of parameters and I don't want to refactor the controller's signature each time
Actually, you don't have to change the controller's signature each time. ASP.NET Core Model binder is able to bind an object from query string automatically. For example, assume you have a simple controller:
[HttpGet("/api/licensee")]
public IActionResult GetLicensee([FromQuery]LicenseeParameters parameters)
{
return Json(parameters);
}
The first time the DTO is:
public class LicenseeParameters
{
public string Name {get;set;}
public string Note {get;set;}
}
What you need is to send a HTTP Request as below:
GET /api/licensee?name=stan¬e=it+works
And later you decide to change the LicenseeParameters:
public class LicenseeParameters
{
public string Name {get;set;}
public string Note {get;set;}
public List<SubNode> Children{get;set;} // a complex array
}
You don't have to change the controller signature. Just send a payload in this way:
GET /api/licensee?name=stan¬e=it+works&children[0].nodeName=it&children[1].nodeName=minus
The conversion is : . represents property and [] represents collection or dictionary.
In case you do want to send a json string within URL, what you need is to create a custom model binder.
internal class LicenseeParametersModelBinder : IModelBinder
{
private readonly JsonSerializerOptions _jsonOpts;
public LicenseeParametersModelBinder(IOptions<JsonSerializerOptions> jsonOpts)
{
this._jsonOpts = jsonOpts.Value;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var name= bindingContext.FieldName;
var type = bindingContext.ModelType;
try{
var json= bindingContext.ValueProvider.GetValue(name).FirstValue;
var obj = JsonSerializer.Deserialize(json,type, _jsonOpts);
bindingContext.Result = ModelBindingResult.Success(obj);
}
catch (JsonException ex){
bindingContext.ModelState.AddModelError(name,$"{ex.Message}");
}
return Task.CompletedTask;
}
}
and register the model binder as below:
[HttpGet("/api/licensee/{parameters}")]
public IActionResult GetLicensee2([ModelBinder(typeof(LicenseeParametersModelBinder))]LicenseeParameters parameters)
{
return Json(parameters);
}
Finally, you can send a json within URL(suppose the property name is case insensive):
GET /api/licensee/{"name":"stan","note":"it works","children":[{"nodeName":"it"},{"nodeName":"minus"}]}
The above two approaches both works for me. But personally I would suggest you use the the first one as it is a built-in feature.
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.
I'm pretty new in .Net community, please show mercy haha. I got two questions:
is there any similar implementation of symfony's #paramconverter annotation in ASP.NET (such as a converter attributes)?
e.g. the request url, no matter GET/POST/PUT/DELETE, the api request with an id of certain entity and we are able to convert/bind such id into a entity object(say productId in int and convert to Product object)
Expected PseudoCode:
[Route("products/{productId}/comments")]
[ProductConverter]
public HttpResponseMessage getProductComments(Product product) {
// product here not only with `productId` field, but already converted/bind into `Product`, through repository/data store
....
}
Is this good practise in .Net? I ask for this implementation because I think this pattern able to reduce duplicate code as API requests mainly rely on object id. I can even throw an exception if such Product object not found by such string.
It looks like ModelBinder is equiuvalent to symphony's parmconverter. You can read more about it here: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
Here is sample:
First you have to implement IModelBinder. That's very basic implementation of it:
public class ProductBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(Product))
{
return false;
}
var id = (int)bindingContext.ValueProvider.GetValue("productId").ConvertTo(typeof(int));
// Create instance of your object
bindingContext.Model = new Product { Id = id };
return true;
}
}
Next you have to configure ASP.NET WebApi to use that binder. In WebApiConfig.cs file (or any other where you configure WebAPI) add the following line:
config.BindParameter(typeof(Product), new ProductBinder());
The final step is to create correct controller method. It's important to provide correct route parameter in order to correct binding.
[Route("products/{productId}/comments")]
public HttpResponseMessage getProductComments(Product product) {
}
I don't think that this is bad practice or good practices. As always it depends. For sure it reduce code duplication and introduce some order to your code. If the object with that id doesn't exists you can even try tweak this binder to return response 404 (not found)
Edit:
Using IModelBinder with dependency injection is a bit tricky but still possible. You have to write additional extension method:
public static void BindParameter(this HttpConfiguration config, Type type, Type binderType)
{
config.Services.Insert(typeof(ModelBinderProvider), 0, new SimpleModelBinderProvider(type, () => (IModelBinder)config.DependencyResolver.GetService(binderType)));
config.ParameterBindingRules.Insert(0, type, param => param.BindWithModelBinding());
}
It's based on orginal method found there. The only difference is that it expect type of the binder instead of instance. So you just call:
config.BindParameter(typeof(Product), typeof(ProductBinder));
I've thought of a few ways of doing this but I want to get the community's view. I have a feeling that the answer is cringeworthily simple - I'm not afraid to look stupid (my kids took that fear away from me long ago!)
I'm writing an XML REST web service using MVC2. All XML Types that consumers of the web service will receive and send are governed by simple but extensive XSD, and these parameters will be bound from xml in the request body via a custom default model binder and value provider.
I have a goodly amount of controllers, each with a goodly amount of action methods (not excessive - just 'goodly' ;) ) - and in nearly every case these action methods are going to be accepting model types that are all reference types.
In practically every case it's going to be an error for the caller not to provide these parameter values, and as such a standard error message such as "The parameter {name} type:{ns:type} is required" can be sent back.
What I want to do is to be able to validate parameters are not null before an action method is executed; and then to return an ActionResult that represents the Error to the client (for this I already have an XMLResult type) without the action method itself having to validdate the parameters itself.
So, instead of:
public ActionResult ActionMethod(RefType model)
{
if(model == null)
return new Xml(new Error("'model' must be provided"));
}
Something like:
public ActionResult ActionMethod([NotNull]RefType model)
{
//model now guaranteed not to be null.
}
I know this is exactly the kind of cross-cutting that can be achieved in MVC.
It seems to me that either a base controller override of OnActionExecuting or a custom ActionFilter is the most likely way of doing this.
I'd also like to be able to extend the system so that it automatically picks up XML schema validation errors (added to ModelState during binding by a custom value provider) thus preventing the action method from continuing if any of the parameter values can't be loaded correctly because the XML request is badly formed.
Here's the implementation that I've come up with (while waiting for any better ideas :) )
It's a generic approach and I think is pretty scalable - allowing for hopefully a similar kind of depth to parameter validation as you get with model validation at the same time as providing the error auto-respond functionality (when model state contains one or more errors) that I was looking for.
I hope this isn't too much code for an SO answer(!); I had a load of documentation comments in there that I've taken out to keep it shorter.
So, in my scenario I have two types of model error that, if they occur, should block execution of the action method:
Failed schema validation of the XML from which a parameter value will be constructed
Missing (null) parameter value
Schema validation is currently performed during model binding, and automatically adds model errors to the ModelState - so that's great. So I need a way to perform the auto-null check.
In the end I created two classes to wrap up the validation:
[AttributeUsage(AttributeTargets.Parameter,
AllowMultiple = false, Inherited = false)]
public abstract class ValidateParameterAttribute : Attribute
{
private bool _continueValidation = false;
public bool ContinueValidation
{ get { return _continueValidation; } set { _continueValidation = value; } }
private int _order = -1;
public int Order { get { return _order; } set { _order = value; } }
public abstract bool Validate
(ControllerContext context, ParameterDescriptor parameter, object value);
public abstract ModelError CreateModelError
(ControllerContext context, ParameterDescriptor parameter, object value);
public virtual ModelError GetModelError
(ControllerContext context, ParameterDescriptor parameter, object value)
{
if (!Validate(context, parameter, value))
return CreateModelError(context, parameter, value);
return null;
}
}
[AttributeUsage(AttributeTargets.Parameter,
AllowMultiple = false, Inherited = false)]
public class RequiredParameterAttribute : ValidateParameterAttribute
{
private object _missing = null;
public object MissingValue
{ get { return _missing; } set { _missing = value; } }
public virtual object GetMissingValue
(ControllerContext context, ParameterDescriptor parameter)
{
//using a virtual method so that a missing value could be selected based
//on the current controller's state.
return MissingValue;
}
public override bool Validate
(ControllerContext context, ParameterDescriptor parameter, object value)
{
return !object.Equals(value, GetMissingValue(context, parameter));
}
public override ModelError CreateModelError
(ControllerContext context, ParameterDescriptor parameter, object value)
{
return new ModelError(
string.Format("Parameter {0} is required", parameter.ParameterName));
}
}
With this I can then do this:
public void ActionMethod([RequiredParameter]MyModel p1){ /* code here */ }
But this on its own doesn't do anything of course, so now we need something to actually trigger the validation, to get the model errors and add them to model state.
Enter the ParameterValidationAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
Inherited = false)]
public class ParameterValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var paramDescriptors = filterContext.ActionDescriptor.GetParameters();
if (paramDescriptors == null || paramDescriptors.Length == 0)
return;
var parameters = filterContext.ActionParameters;
object paramvalue = null;
ModelStateDictionary modelState
= filterContext.Controller.ViewData.ModelState;
ModelState paramState = null;
ModelError modelError = null;
foreach (var paramDescriptor in paramDescriptors)
{
paramState = modelState[paramDescriptor.ParameterName];
//fetch the parameter value, if this fails we simply end up with null
parameters.TryGetValue(paramDescriptor.ParameterName, out paramvalue);
foreach (var validator in paramDescriptor.GetCustomAttributes
(typeof(ValidateParameterAttribute), false)
.Cast<ValidateParameterAttribute>().OrderBy(a => a.Order)
)
{
modelError =
validator.GetModelError(filterContext, paramDescriptor, paramvalue);
if(modelError!=null)
{
//create model state for this parameter if not already present
if (paramState == null)
modelState[paramDescriptor.ParameterName] =
paramState = new ModelState();
paramState.Errors.Add(modelError);
//break if no more validation should be performed
if (validator.ContinueValidation == false)
break;
}
}
}
base.OnActionExecuting(filterContext);
}
}
Whew! Nearly there now...
So, now we can do this:
[ParameterValidation]
public ActionResult([RequiredParameter]MyModel p1)
{
//ViewData.ModelState["p1"] will now contain an error if null when called
}
To complete the puzzle we need something that can investigate the model errors and automatically respond if there are any. This is the least tidy of the classes (I hate the name and the parameter type used) and I'll probably change it in my project, but it works so I'll post it anyway:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
Inherited = false)]
public abstract class RespondWithModelErrorsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
ModelStateDictionary modelState =
filterContext.Controller.ViewData.ModelState;
if (modelState.Any(kvp => kvp.Value.Errors.Count > 0))
filterContext.Result = CreateResult(filterContext,
modelState.Where(kvp => kvp.Value.Errors.Count > 0));
base.OnActionExecuting(filterContext);
}
public abstract ActionResult CreateResult(
ActionExecutingContext filterContext,
IEnumerable<KeyValuePair<string, ModelState>> modelStateWithErrors);
}
In my application I have an XmlResult that takes a Model instance and serializes to the response using either DataContractSerializer or XmlSerializer - so I've then created RespondWithXmlModelErrorsAttribute that inherits from this last type to formulate one of those with the model as an Errors class that simply contains each of the model errors as strings. The Response Code is also automatically set to 400 Bad Request.
Thus, now I can do this:
[ParameterValidation]
[RespondWithXmlModelErrors(Order = int.MaxValue)]
public ActionResult([RequiredParameter]MyModel p1)
{
//now if p1 is null, the method won't even be called.
}
In the case of web pages this last stage won't necessarily be required, since model errors are typically included in a re-rendering of page that sent the data in the first place, and the existing MVC approach suits this fine.
But for web services (either XML or JSON) being able to offload error reporting to something else makes writing the actual action method a lot easier - and much more expressive, I feel.
Well you could add constraints using regular expressions to individual route values. Then, if these constraints are not upheld, the action method will not be hit:
routes.MapRoute ("SomeWebService", "service/{userId}",
new { controller = "Service", action = "UserService" },
new { userId = #"\d+" });
Alternatively you could create custom constraints to validate route values together as a pack. This would probably be a better strategy for you. Have a look here: Creating a Custom Route Constraint