Kestrel only allows 30MB of request body in the POST request. I am changing the request body limit on a particular action method using [RequestSizeLimit(100000000)]
I would like to pass "100000000" value through configuration. Is there a way to do it?
Action
[HttpPost]
[RequestSizeLimit(100000000)]
public IActionResult MyAction(MyViewModel data)
{
}
This works fine but I am not able to pass it through configuration/appsettings
RequestSizeLimit is an attribute that implements IFilterFactory and returns RequestSizeLimitFilter in CreateInstance() method. You can implement similar filter that will read this limit from configuration:
public class RequestSizeLimitFromConfigAttribute : Attribute, IFilterFactory
{
private string _configurationKey;
public RequestSizeLimitFromConfigAttribute(string configurationKey)
{
_configurationKey = configurationKey;
}
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var filter = serviceProvider.GetRequiredService<RequestSizeLimitFilter>();
var config = serviceProvider.GetRequiredService<IConfiguration>();
filter.Bytes = config.GetValue<int>(_configurationKey);
return filter;
}
...
}
Related
I have the following asp.net WebApi2 route using .NET 4.6 that illustrates the problem I am having:
[Route("books/{id}")]
[HttpGet]
public JsonResponse GetBooks(string id, [FromUri]DescriptorModel model)
With the following model:
public class DescriptorModel
{
public bool Fiction { get; set; } = false;
// other properties with default arguments here
}
I am trying to allow Fiction property to be set to a default value (if not specified during the get request).
When I specify the Fiction property explicitly it works correctly:
curl -X GET --header 'Accept: application/json' 'http://127.0.0.1:11000/api/v1/books/516.375/?Fiction=false'
However, when doing the following test (omitting the property with the default argument):
curl -X GET --header 'Accept: application/json' 'http://127.0.0.1:11000/api/v1/books/516.375'
The value of "model" is bound as null which is not what I am looking for. My question is how to simply allow models defined with default values to be instantiated as such during/after the model binding process but prior to the controller's "GetBooks" action method being called.
NOTE. the reason I use models with GET requests is that documenting in swagger is much easier as then my GET/POST actions can reuse the same models in many case via inheritance.
Since you are using id as FromUri, the only way you can use a model with get is to use url with a query string
[Route("~/GetBooks/{id?}")]
[HttpGet]
public IActionResult GetBooks(string id, [FromQuery] DescriptorModel model)
in this case you url should be
'http://127.0.0.1:11000/api/v1/books/?Name=name&&fiction=true'
//or if fiction==false just
'http://127.0.0.1:11000/api/v1/books/?Name=name'
//or if want to use id
'http://127.0.0.1:11000/api/v1/books/123/?Name=name&&fiction=true'
using model your way will be working only with [FromForm] or [FromBody].
To use it as MVC recomends try this
[Route("books/{id}/{param1}/{param2}/{fiction?}")]
[HttpGet]
public JsonResponse GetBooks(string id, string param1, string param2, bool fiction)
By the way, you don't need to make bool false as default since it is false by default any way
if you want to use ID and DescriptorModel from uri you can do this only if you add Id to DescriptorModel too
[Route("books/{id}/{param1}/{param2}/{fiction?}")]
[HttpGet]
public JsonResponse GetBooks(DescriptorModel model)
UPDATE
If your mvc doesnt support [FromQuery], you can use RequestQuery inside of action like this
var value= context.Request.Query["value"];
but is better to update to MVC 6.
I wasn't able to figure out how to do this via model-binding but I was able to use Action Filters to accomplish the same thing.
Here's the code I used (note it only supports one null model per action but this could easily be fixed if needed):
public class NullModelActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext context)
{
object value = null;
string modelName = string.Empty;
// are there any null models?
if (context.ActionArguments.ContainsValue(null))
{
// Yes => iterate over all arguments to find them.
foreach (var arg in context.ActionArguments)
{
// Is the argument null?
if (arg.Value == null)
{
// Search the parameter bindings to find the matching argument....
foreach (var parameter in context.ActionDescriptor.ActionBinding.ParameterBindings)
{
// Did we find a match?
if (parameter.Descriptor.ParameterName == arg.Key)
{
// Yes => Does the type have the 'Default' attribute?
var type = parameter.Descriptor.ParameterType;
if (type.GetCustomAttributes(typeof(DefaultAttribute), false).Length > 0)
{
// Yes => need to instantiate it
modelName = arg.Key;
var constructor = parameter.Descriptor.ParameterType.GetConstructor(new Type[0]);
value = constructor.Invoke(null);
// update the model state
context.ModelState.Add(arg.Key, new ModelState { Value = new ValueProviderResult(value, value.ToString(), CultureInfo.InvariantCulture) });
}
}
}
}
}
// update the action arguments
context.ActionArguments[modelName] = value;
}
}
}
I created a DefaultAttribute class like so:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class DefaultAttribute : Attribute
{
}
I then added that attribute to my descriptor class:
[Default]
public class DescriptorModel
{
public bool Fiction { get; set; } = false;
// other properties with default arguments here
}
And finally registered the action filter in
public void Configure(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
// lots of configuration here omitted
config.Filters.Add(new NullModelActionFilter());
appBuilder.UseWebApi(config);
}
I definitely consider this a hack (I think I really should be doing this via model binding) but it accomplishes what I needed to do with the constraints that I was given of ASP.NET (not Core) / WebApi2 / .NET Framework so hopefully some else will benefit from this.
Summary
Given an HTTP request with a string body "hamburger"
I want to be able to bind the entire body of the request to a string parameter in the controller action's method signature.
When calling this controller by making an HTTP request to the relative URL string-body-model-binding-example/get-body I get an error and the action is never called
Controller
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace MyProject
{
[Route("string-body-model-binding-example")]
[ApiController]
public class ExampleController: ControllerBase
{
[HttpPut("get-body")]
public string GetRequestBody(string body)
{
return body;
}
}
}
Integration Test Demonstrating the Problem
using FluentAssertions;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
public class MyIntegrationTests : MyIntegrationTestBase
{
[Fact]
public async Task String_body_is_bound_to_the_actions_body_parameter()
{
var body = "hamburger";
var uri = "string-body-model-binding-example/get-body";
var request = new HttpRequestMessage(HttpMethod.Put, uri)
{
Content = new StringContent(body, Encoding.UTF8, "text/plain")
};
var result = await HttpClient.SendAsync(request);
var responseBody = await result.Content.ReadAsStringAsync();
responseBody.Should().Be(body,
"The body should have been bound to the controller action's body parameter");
}
}
Note: in the above example test HttpClient is setup using Microsoft.AspNetCore.Mvc.Testing https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1. My other controller actions with a POCO model in the action method signature are reachable so I know there is something wrong with how I am trying to do my model binding.
Edit: Things I've tried:
Adding [FromBody] to the parameter => 415 Unsupported Media type
Removing [ApiController] from the controller => Action is hit but body is null
Adding [FromBody] to the parameter and removing [ApiController] from the controller => 415 Unsupported Media type
Adding [Consumes("text/plain")] to action w/wout [ApiController] and w/wout [FromBody]
Sending the request with a content type of application/json with any of the above combinations => error or null depending on the option
It surprises me that string isn't one of the supported primitives
Not sure if this possible to achieve with the framework means but you can create a custom model binder for this
public class RawBodyModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
using (var streamReader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
string result = await streamReader.ReadToEndAsync();
bindingContext.Result = ModelBindingResult.Success(result);
}
}
}
And use it like this
[HttpPut("get-body")]
public string GetRequestBody([ModelBinder(typeof(RawBodyModelBinder))] string body)
{
return body;
}
Or you can tell the framework to use your model binder in more elegant way using IModelBinderProvider. First introduce new BindingSource as a singleton
public static class CustomBindingSources
{
public static BindingSource RawBody { get; } = new BindingSource("RawBod", "Raw Body", true, true);
}
And create our [FromRawBody] attribute
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromRawBodyAttribute : Attribute, IBindingSourceMetadata
{
public BindingSource BindingSource => CustomBindingSources.RawBody;
}
The framework treats IBindingSourceMetadata attributes in special way and gets its BindingSource value for us so it can be used in model binder provider.
Then create IModelBinderProvider
public class RawBodyModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
//use binder if parameter is string
//and FromRawBody specified
if (context.Metadata.ModelType == typeof(string) &&
context.BindingInfo.BindingSource == CustomBindingSources.RawBody)
{
return new RawBodyModelBinder();
}
return null;
}
}
Add the model binder provider in Startup
services
.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new RawBodyModelBinderProvider());
//..
}
Use it as the following
[HttpPut("get-body")]
public string GetRequestBody([FromRawBody] string body)
{
return body;
}
I have a Model called Issues which have Id and severity attributes which are integers and an Issue Controller which should handle GET requests and return All Issues and by Severity. Since Dotnet API doesn't support the same parameter type such as int, how do I do this?
I tried using different method names such as Get() and GetBySeverity(int severity) but ambiguous exception arose.
this is my controller
[Route("api/[controller]")]
[ApiController]
public class IssuesController : ControllerBase
{
[HttpGet(Name="issues")]
public IEnumerable<Issue> GetAllIssues() {
IssueService service = new IssueService();
return service.GetIssues();
}
[HttpGet]
public IEnumerable<Issue> GetBySeverity([FromQuery(Name="severity")]int severity)
{
IssueService service = new IssueService();
return service.GetIssuesBySeverity(severity);
}
}
these are the apis which i want to develop
/api/issues/ and
/api/issues/?severity=1
like this
//GET api/issues/5
[HttpGet("{id}")]
public Issue GetByRoute([FromRoute(Name="id")] int id)
{
IssueService service = new IssueService();
return service.GetIssueById(id);
}
[HttpGet()]
public Issue GetByQuery([FromQuery(Name="severity")] int severityId)
{
IssueService service = new IssueService();
return service.GetIssueById(severityId);
}
There is an ambiguous reference because both actions are HttpGet and the routing engine cannot decide which route to take or which route you meant.
Web Api controllers route using HttpVerbs rather than the action/method name (like MVC).
I would use Attribute routing with Http[Verb] attributes. This uses Route("myroute") to define the route.
// GET api/issues/severity/5
[Route("severity/{severity}")]
[HttpGet()]
public IEnumerable<Issue> GetBySeverity(int severity)
{
IssueService service = new IssueService();
return service.GetIssuesBySeverity(severity);
}
// GET api/issues/5
[HttpGet("{id}")]
public Issue Get(int id)
{
IssueService service = new IssueService();
return service.GetIssueById(id);
}
The above methods would route to api/issues/1 and api/issues/severity/1. If you want to map using query param rather than url param then the below should work:
// GET api/issues/severity?severity=5
[HttpGet("severity")]
public IEnumerable<Issue> GetBySeverity([FromQuery] int severity)
{
IssueService service = new IssueService();
return service.GetIssuesBySeverity(severity);
}
// GET api/issues?id=5
[HttpGet()]
public Issue Get([FromQuery] int id)
{
IssueService service = new IssueService();
return service.GetIssueById(id);
}
The above actions will route to api/issues?id=1 and api/issues/severity?severity=1
I've got a following two controllers:
[RoutePrefix("/some-resources")
class CreationController : ApiController
{
[HttpPost, Route]
public ... CreateResource(CreateData input)
{
// ...
}
}
[RoutePrefix("/some-resources")
class DisplayController : ApiController
{
[HttpGet, Route]
public ... ListAllResources()
{
// ...
}
[HttpGet, Route("{publicKey:guid}"]
public ... ShowSingleResource(Guid publicKey)
{
// ...
}
}
All three actions got in fact three different routes:
GET /some-resources
POST /some-resources
GET /some-resources/aaaaa-bbb-ccc-dddd
If I put them into single controller everything works just fine, however if I separate them (as shown above) WebApi throws following exception:
Multiple controller types were found that match the URL. This can
happen if attribute routes on multiple controllers match the requested
URL.
This message is quite obvious. It seems WebApi does not take HTTP method into account when looking for a right candidate for controller/action.
How could I achieve the expected behavior?
UPDATE: I've digged a little into Web API internals and I understand that's the way it works by default. My goal is to separate the code and logic - in real world case those controllers have different dependencies and are a bit more complex. For the sake of maintenance, testability, project organization etc. they should be different objects (SOLID and stuff).
I thought I could override some WebAPI services (IControllerSelector etc) however this seems to be a little bit risky and non-standard approach for this simple and - as I assumed - common case.
UPDATE
Based on your comments, updated question and the answer provided here
Multiple Controller Types with same Route prefix ASP.NET Web Api
Desired result can be achieved via custom route constraints for the HTTP method applied to controller actions.
On inspection of the default Http{Verb} attributes ie [HttpGet], [HttpPost] and the RouteAttribute, which by the way are sealed, I realized that their functionality can be combine into one class similar to how they are implemented in Asp.Net-Core.
The following is for GET and POST, but it shouldn't be difficult to create constraints for the other HTTP methods PUT, DELETE...etc to be applied to the controllers.
class HttpGetAttribute : MethodConstraintedRouteAttribute {
public HttpGetAttribute(string template) : base(template, HttpMethod.Get) { }
}
class HttpPostAttribute : MethodConstraintedRouteAttribute {
public HttpPostAttribute(string template) : base(template, HttpMethod.Post) { }
}
The important class is the route factory and the constraint itself. The framework already has base classes that take care of most of the route factory work and also a HttpMethodConstraint so it is just a matter of applying the desired routing functionality.
class MethodConstraintedRouteAttribute
: RouteFactoryAttribute, IActionHttpMethodProvider, IHttpRouteInfoProvider {
public MethodConstraintedRouteAttribute(string template, HttpMethod method)
: base(template) {
HttpMethods = new Collection<HttpMethod>(){
method
};
}
public Collection<HttpMethod> HttpMethods { get; private set; }
public override IDictionary<string, object> Constraints {
get {
var constraints = new HttpRouteValueDictionary();
constraints.Add("method", new HttpMethodConstraint(HttpMethods.ToArray()));
return constraints;
}
}
}
So given the following controller with the custom route constraints applied...
[RoutePrefix("api/some-resources")]
public class CreationController : ApiController {
[HttpPost("")]
public IHttpActionResult CreateResource(CreateData input) {
return Ok();
}
}
[RoutePrefix("api/some-resources")]
public class DisplayController : ApiController {
[HttpGet("")]
public IHttpActionResult ListAllResources() {
return Ok();
}
[HttpGet("{publicKey:guid}")]
public IHttpActionResult ShowSingleResource(Guid publicKey) {
return Ok();
}
}
Did an in-memory unit test to confirm functionality and it worked.
[TestClass]
public class WebApiRouteTests {
[TestMethod]
public async Task Multiple_controllers_with_same_URL_routes_but_different_HTTP_methods() {
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
var errorHandler = config.Services.GetExceptionHandler();
var handlerMock = new Mock<IExceptionHandler>();
handlerMock
.Setup(m => m.HandleAsync(It.IsAny<ExceptionHandlerContext>(), It.IsAny<System.Threading.CancellationToken>()))
.Callback<ExceptionHandlerContext, CancellationToken>((context, token) => {
var innerException = context.ExceptionContext.Exception;
Assert.Fail(innerException.Message);
});
config.Services.Replace(typeof(IExceptionHandler), handlerMock.Object);
using (var server = new HttpTestServer(config)) {
string url = "http://localhost/api/some-resources/";
var client = server.CreateClient();
client.BaseAddress = new Uri(url);
using (var response = await client.GetAsync("")) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
using (var response = await client.GetAsync("3D6BDC0A-B539-4EBF-83AD-2FF5E958AFC3")) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
using (var response = await client.PostAsJsonAsync("", new CreateData())) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
}
public class CreateData { }
}
ORIGINAL ANSWER
Referencing : Routing and Action Selection in ASP.NET Web API
That's because it uses the routes in the route table to find the controller first and then checks for Http{Verb} to select an action. which is why it works when they are all in the same controller. if it finds the same route to two different controllers it doesn't know when one to select, hence the error.
If the goal is simple code organization then take advantage of partial classes
ResourcesController.cs
[RoutePrefix("/some-resources")]
partial class ResourcesController : ApiController { }
ResourcesController_Creation.cs
partial class ResourcesController {
[HttpPost, Route]
public ... CreateResource(CreateData input) {
// ...
}
}
ResourcesController_Display.cs
partial class ResourcesController {
[HttpGet, Route]
public ... ListAllResources() {
// ...
}
[HttpGet, Route("{publicKey:guid}"]
public ... ShowSingleResource(Guid publicKey) {
// ...
}
}
I'm creating an API using C# and ASP.NET Web API and I want it to return an error when a parameter is used that isn't recognised.
For example:
/api/Events
should a list of events
/api/Events?startTime={{startTime}}
should return a list of events that started at a particular time
/api/Events?someRandomInvalidParameter={{something}}
should return an error
Is there a nice config way to do this? If not, how can I get a list of parameters to check myself.
You could create an ActionFilter to automate this:
public class InvalidQueryStringRejectorAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var arguments = actionContext.ActionArguments.Keys;
var queryString = actionContext.Request.GetQueryNameValuePairs()
.Select(q => q.Key);
var invalidParams = queryString.Where(k => !arguments.Contains(k));
if (invalidParams.Any())
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, new
{
message = "Invalid query string parameters",
parameters = invalidParams
});
}
}
}
That filter will reject any request with query string parameters that do not match the method signature.
You may use it like this:
[InvalidQueryStringRejector]
public IHttpActionResult Get(string value)
{
return Ok(value);
}
Or apply to any action by registering it inside your HttpConfiguration object:
config.Filters.Add(new InvalidQueryStringRejectorAttribute());
try strongly typed actions like this
public string Get()
{
return "I'm alive empty";
}
public string Get([FromUri] int id)
{
return "I'm alive";
}
So normal call will return I'm alive or I'm alive empty
http://localhost:1578/api/alive?id=1 //OR
http://localhost:1578/api/alive
But if you try to call it like this
http://localhost:1578/api/alive?blablabla=1
You will occure this error
The requested resource does not support http method 'GET'.
I think you should be override methods
Example:
[Route("/app/Events/{startTime})"]
public ApiResponse Get(string startTime)
{
}
[Route("/app/Events/{startTime}/{someRandomeInvalid}")]
public ApiResponse Get(string startTime, string someRandomeInvalid)
{
}
Don't set explicit parameters name. You should be manage/check by order of parameter