I have a very simple model:
public class FoobarGETRequestModel {
[Required]
public string Id { get; set; }
}
It's being used for binding like so:
[HttpGet]
[ValidateModel] //This is a custom attribute for handling validation errors
public async Task<HttpResponseMessage> Foobar([FromUri]FoobarGETRequestModel model) {
...
}
If I make a request to /api/controller/foobar (notice I'm not providing an Id parameter), the ModelState.IsValid property always returns true (from both an action filter and from within the method above).
How do I bind via the URI and still leverage the frameworks ModelState validation?
Edit: Here is what ValidateModel looks like:
if (actionContext.ModelState.IsValid == false) {
var responseObject = new FooApiExceptionResponse() {
Errors = actionContext.ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage)
};
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, responseObject);
}
Did you check your RouteTable? If you are telling routetable that your "id" parameter is optional in default route, this could occur.
Related
I have an ASP.NET Web API 2 project to which I have added Swagger - Swashbuckle v5.6.0. Everything works fine. Swagger UI renders test endpoints for my API as expected.
I added a new Controller to my API. There is a GET action with a complex type parameter. For complex types, Web API tries to read the value from the message body. This is the default behaviour.
Here is my GET action:
[HttpGet]
[Route("search")]
[ResponseType(typeof(List<SearchModel>))]
public IHttpActionResult Search(SearchModel searchOptions)
{
//....
return Ok();
}
And her is my complex type:
public class SearchModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
public string AddressLine1 { get; set; }
public string City { get; set; }
public string Telephone { get; set; }
public string MobilePhone { get; set; }
}
The problem:
But Swagger UI doesn't render body parameter field for my complex type in the GET action. For POST and PUT actions Swagger UI renders body parameter fields as expected but not for the complex type in my GET action.
As can be seen in the screenshot Swagger UI renders query parameters fields for attributes in my complex type instead of rendering a body parameter field for my type as it does in the case of POST and PUT.
My GET action is working fine when testing from Postman and filling the json in the body of the request. By setting breakpoint in the action inside Visual Studio I can see the values are bound to my object in the action parameter.
I have tried to decorate the parameter in my action with [FromBody] (which is the default for complex type) but same result.
Is this a bug in Swagger? Or am I missing something?
Sadly, you can't do what you want with Swagger. You can't send a request model in an HTTP GET method. You can however change the swagger UI to look like this:
but you won't be able to receive the model in your controller.
This is a known issue within the Swagger developers and it was discussed in 2016 and the final decision is that swagger won't support a request body in an HTTP GET method. Here is the link to the already closed issue.
You have three options here:
Leave the method as it is, and test it in Postman, but not in Swagger.
Follow the below steps to achieve the picture above, but please note, that it will only fix the UI part and you will always end up with null SearchModel in the controller when you press Try it out! in swagger.
Make it a [HttpPost method instead of [HttpGet].
How to make swagger UI display GET method with request body:
First, create one Attribute class:
public class ModelInBodyAttribute : Attribute
{
public ModelInBodyAttribute(string modelName, string description, bool isRequired)
{
this.ModelName = modelName;
this.Description = description;
this.IsRequired = IsRequired;
}
public string ModelName { get; set; }
public bool IsRequired { get; set; }
public string Description { get; set; }
}
Then you can decorate your method in the controller:
[ModelInBody(modelName: nameof(SearchModel), description: "My model description", isRequired: true)]
[HttpGet]
[Route("search")]
[ResponseType(typeof(List<SearchModel>))]
public IHttpActionResult Search(SearchModel searchOptions)
{
//....
return Ok(new List<SearchModel>());
}
After that create IOperationFilter class (ModelInBodyOperationFilter):
public class ModelInBodyOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var attribute = apiDescription.GetControllerAndActionAttributes<ModelInBodyAttribute>().FirstOrDefault();
if (attribute == null)
{
return;
}
operation.parameters.Clear();
operation.parameters.Add(new Parameter
{
name = attribute.ModelName,
description = attribute.Description,
#in = "body",
required = attribute.IsRequired,
schema = new Schema { #ref = $"#/definitions/{attribute.ModelName}" }
});
}
}
Lastly, don't forget to register the IOperationFilter in SwaggerConfig:
c.OperationFilter<ModelInBodyOperationFilter>();
When you send the request via swagger, you will notice that the Curl part is absolutely correct, but still, in your controller there is nothing.
There are endless discussions on whether you should have a PAYLOAD "Body content" in a GET request. As you mentioned it's supported by HTTP but you will find in the internet that many people suggest not to do it. I guess that swagger team also expect you not to use it.
Say you've got a model that looks like
public class UserModel
{
public string UserName { get; set; }
public DateTime? DateOfBirth { get; set; }
}
The DateOfBirth field isn't required, but could be specified. You have a Web API POST endpoint that looks like
[Route("")]
[AcceptVerbs("POST")]
public async Task<IHttpActionResult> Create(UserModel user)
{
}
And we've set the JSON serializer in start up like so,
public static void Register(HttpConfiguration config)
{
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
var settings = jsonFormatter.SerializerSettings;
settings.Converters.Add(new IsoDateTimeConverter());
settings.Error += (sender, args) => Console.WriteLine("This event is fired ok");
}
If we send some JSON to the endpoint that looks like this
{
"userName": "User1",
"dateOfBirth": "jhdgjhjfg"
}
...the error event is fired in the Serializer settings and the endpoint is called. At this point, the DateOfBirth field is null and I don't have any context that a deserialization error has occurred
Reading the JSON.Net documentation, because Handled == false in the Error event arguments of the Settings object, an exception should be raised into the application code - this doesn't happen? Is there a setting I haven't configured correctly for this?
How can I get context within the action so that I know a value was specified for a field and couldn't be deserialized? Even global behaviour would be fine, as long as I know this has happened and can return a 400.
UPDATE:
We can use a filter to check the Model state, then check the Model State errors for exceptions of type JsonReaderException. This lets you return a 400 with a list of violating fields
public class CheckJsonExceptionModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid)
{
return;
}
var fieldsInError = new List<string>();
foreach (var jsonException in
actionContext.ModelState.Keys.SelectMany(key => actionContext.ModelState[key].Errors)
.Select(error => error.Exception).OfType<JsonReaderException>())
{
Trace.TraceError(jsonException.Message);
fieldsInError.Add(jsonException.Path);
}
var apiError = new { ErrorMessages.BadRequestModel.Message, FieldsInError = new List<string>() };
foreach (var fieldError in fieldsInError)
{
apiError.FieldsInError.Add(fieldError);
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, apiError);
}
}
You have multiple options. But first, you are getting no exception because the WebApi handles this exception. Bad news.
Good news, you can handle it in at least two ways; use the ModelState.IsValid property - in your case it will be false. You can access them in your post-method. When you remove the invalid dateOfBirth it is true ;-)
Or you can use an ActionFilterAttribute to put it on your methods for re-use purposes.
For example:
public async Task<IHttpActionResult> Create(UserModel user) {
if (!ModelState.IsValid) {
// ModelState.Keys // Get all error-keys
}
}
I am trying to write a custom validation attribute in MVC and I can't make it work as an unobstructive validation. It works fine with postback (kinda) but as the form is on a dialog, I must have an Ajax style call or it's unusable. Maybe what i am trying to do is unachieveable. The problem is i need to connect to a database to do the check.
I made a simple demo for my problem.
My model
public class Customer
{
public int Id { get; set; }
[IsNameUnique]
[Required]
public string Name { get; set; }
}
The view:
#model WebApplication1.Models.Customer
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { #id = "NewForm" }))
{
#Html.ValidationSummary(true)
#Html.EditorFor(m => m.Name)
#Html.ValidationMessageFor(m => m.Name)
<br />
<input type="submit" value="Submit" />
}
Custom validation class
public class IsNameUnique : ValidationAttribute
{
private CustomerRepository _repository;
public IsNameUnique()
{
_repository = new CustomerRepository();
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if(value != null)
{
var isValid = _repository.IsNameUnique(value);
if(!isValid)
{
return new ValidationResult("Name must be unique");
}
}
return ValidationResult.Success;
}
}
Post method
[HttpPost]
public ActionResult Index(Customer customer)
{
if(ModelState.IsValid)
{
//add customer
}
return View();
}
database call
class CustomerRepository
{
internal bool IsNameUnique(object value)
{
//call to database
return false;
}
}
There is a form with a name field. I need to check if name is already in the database.
My question is how can I do unobtrusive style validation in my case? I found other posts on SO about IClientValidatable but none of them really show what I need to do. i.e. none of them do check against a database. Thanks.
Basically "unobtrusive validation" means "Client-Side validation, defined in an unobtrusive way". Key point here is "Client-Side", that is, validation which can be done via JavaScript in client browser.
Checking for name uniqueness involves server-side resources, and while it can be done in JavaScript using AJAX requests to server, usually people decide not to do so.
You can follow this guide for details of implementing unobtrusive validation: http://thewayofcode.wordpress.com/tag/custom-unobtrusive-validation/
In general you will need to do the following:
Enable unobtrusive validation in web.config
Include jQuery, jQuery Validate and unobtrusive scripts into your page
Implement IClientValidatable for your custom validation attribute
Implement and register client-side rules for your custom attribute
You may want to look into the [Remote] validation attribute. Just make a controller method that returns a JsonResult and map it to the remote attribute. This is probably the easiest way to accomplish what you're looking to do.
[Remote( "IsNameUnique", "Customers", HttpMethod = "post" )]
public override string Name { get; set; }
[HttpPost]
public JsonResult IsNameUnique( string name )
{
// Code
}
If you want to implement this as a custom validation, you need to do the following:
In your attribute, implement IClientValidatable. This requires you to implement GetClientValidationRules() method. Return a new client validation rule with your type and parameters.
Here's an example:
https://github.com/DustinEwers/dot-net-mvc-ui-demos/blob/master/ASPNET4/UIDemos/UIDemos/Attributes/PastDateOnlyAttribute.cs
Then you need to implement a jQuery validation rule. This is where you'd make your ajax call:
jQuery.validator.addMethod("pastdateonly", function (val, element, params) {
var value = $.trim($(element).val());
if (value === "") return true;
var maxDate = params.maxdate,
dteVal = new Date(value),
mxDte = new Date(maxDate);
return dteVal < mxDte;
});
Then add an unobtrusive adapter method.
jQuery.validator.unobtrusive.adapters.add("pastdateonly", ["maxdate"],
function (options) {
options.rules["pastdateonly"] = {
maxdate: options.params.maxdate
};
options.messages["pastdateonly"] = options.message;
}
);
Example:
https://github.com/DustinEwers/dot-net-mvc-ui-demos/blob/master/ASPNET4/UIDemos/UIDemos/Scripts/site.js
First of all, I'm still a beginner in MVC
Obviously anyone knows that we can pass by /CONTROLLER/METHOD/ID, but in some cases, like I need to pass 3-4 params into the controller, how should I do? Is there a good way of passing them all?
The code below is a controller dealing with post request, why I cannot use the "temp1"? it said that one should be declared before use, what that means?
[HttpPost]
public ActionResult Payment_Home(Wires_SWIFT temp1){
string str = temp1.BENEFICIARY_NAME;
DbQuery<Wires_SWIFT> dbq = db.Wires_SWIFT.Where(d => d.BENEFICIARY_NAME LIKE temp1.);
return View();
}
Use a ViewModel.
This is a class that contains all the values you need - you populate it in the controller action and pass it in to the view (helps if the view is strongly typed to the model.
public class MyModel
{
public string SomeValue { get; set; }
public string SomeOtherValue { get; set; }
}
// controller
var myModel = new MyModel...
return View(myModel);
// view
#model MyModel
#Model.SomeValue
You need to make sure you have a route set correctly.
so to match dothis/action/param1/param2 you'd want a route like:
routes.MapRoute(
"MyRoute", // Route name
"{controller}/{action}/{param1}/{param2}", // URL with parameters
new { controller = "Surveys", action = "Index", param1 = "param1", param2 = "param2" } // Parameter defaults
);
For the above route to actually work, you'll need a controller and action such as
public class DoThisController : Controller
....
public ActionResult Action(int param1, int param2)
....
The type of the action parameters doesn't matter, but the name should match the name defined in the rule to allow the action to be located by ASP.Net
In your case, the error is because your parameter is called temp1 & in the route data there is nothing called that.
There are a lot of articles devoted to working with data in MVC, and nothing about MVC 2.
So my question is: what is the proper way to handle POST-query and validate it.
Assume we have 2 actions. Both of them operates over the same entity, but each action has its own separated set of object properties that should be bound in automatic manner. For example:
Action "A" should bind only "Name" property of object, taken from POST-request
Action "B" should bind only "Date" property of object, taken from POST-request
As far as I understand - we cannot use Bind attribute in this case.
So - what are the best practices in MVC2 to handle POST-data and probably validate it?
UPD:
After Actions performed - additional logic will be applied to the objects so they become valid and ready to store in persistent layer. For action "A" - it will be setting up Date to current date.
I personally don't like using domain model classes as my view model. I find it causes problems with validation, formatting, and generally feels wrong. In fact, I'd not actually use a DateTime property on my view model at all (I'd format it as a string in my controller).
I would use two seperate view models, each with validation attributes, exposed as properties of your primary view model:
NOTE: I've left how to combining posted view-models with the main view model as an exercise for you, since there's several ways of approaching it
public class ActionAViewModel
{
[Required(ErrorMessage="Please enter your name")]
public string Name { get; set; }
}
public class ActionBViewModel
{
[Required(ErrorMessage="Please enter your date")]
// You could use a regex or custom attribute to do date validation,
// allowing you to have a custom error message for badly formatted
// dates
public string Date { get; set; }
}
public class PageViewModel
{
public ActionAViewModel ActionA { get; set; }
public ActionBViewModel ActionB { get; set; }
}
public class PageController
{
public ActionResult Index()
{
var viewModel = new PageViewModel
{
ActionA = new ActionAViewModel { Name = "Test" }
ActionB = new ActionBViewModel { Date = DateTime.Today.ToString(); }
};
return View(viewModel);
}
// The [Bind] prefix is there for when you use
// <%= Html.TextBoxFor(x => x.ActionA.Name) %>
public ActionResult ActionA(
[Bind(Prefix="ActionA")] ActionAViewModel viewModel)
{
if (ModelState.IsValid)
{
// Load model, update the Name, and commit the change
}
else
{
// Display Index with viewModel
// and default ActionBViewModel
}
}
public ActionResult ActionB(
[Bind(Prefix="ActionB")] ActionBViewModel viewModel)
{
if (ModelState.IsValid)
{
// Load model, update the Date, and commit the change
}
else
{
// Display Index with viewModel
// and default ActionAViewModel
}
}
}
One possible way to handle POST data and add validation, is with a custom model binder.
Here is a small sample of what i used recently to add custom validation to POST-form data :
public class Customer
{
public string Name { get; set; }
public DateTime Date { get; set; }
}
public class PageController : Controller
{
[HttpPost]
public ActionResult ActionA(Customer customer)
{
if(ModelState.IsValid) {
//do something with the customer
}
}
[HttpPost]
public ActionResult ActionB(Customer customer)
{
if(ModelState.IsValid) {
//do something with the customer
}
}
}
A CustomerModelBinder will be something like that:
public class CustomerModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name == "Name") //or date or whatever else you want
{
//Access your Name property with valueprovider and do some magic before you bind it to the model.
//To add validation errors do (simple stuff)
if(string.IsNullOrEmpty(bindingContext.ValueProvider.GetValue("Name").AttemptedValue))
bindingContext.ModelState.AddModelError("Name", "Please enter a valid name");
//Any complex validation
}
else
{
//call the usual binder otherwise. I noticed that in this way you can use DataAnnotations as well.
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
and in the global.asax put
ModelBinders.Binders.Add(typeof(Customer), new CustomerModelBinder());
If you want not to bind Name property (just Date) when you call ActionB, then just make one more custom Model Binder and in the "if" statement, put to return the null, or the already existing value, or whatever you want. Then in the controller put:
[HttpPost]
public ActionResult([ModelBinder(typeof(CustomerAModelBinder))] Customer customer)
[HttpPost]
public ActionResult([ModelBinder(typeof(CustomerBModelBinder))] Customer customer)
Where customerAmodelbinder will bind only name and customerBmodelbinder will bind only date.
This is the easiest way i have found, to validate model binding, and i have achieved some very cool results with complex view models. I bet there is something out there that i have missed, and maybe a more expert can answer.
Hope i got your question right...:)