Is it possible to set an optional [Required] attribute, applicable on PATCH or PUT. I have the following code but no matter what the controller call it will always be required.
public class Car
{
[DataMember(Order = 0)]
public string CarId { get; set; }
[DataMember(Order = 1)]
[Required]
public string IsIncluded { get; set; }
}
Controller;
[HttpPatch]
public HttpResponseMessage PatchCar(Car car)
{
// check if submitted body is valid
if (!ModelState.IsValid)
{
// Something is bad!
}
}
What I want is something like the following;
public class Car
{
[DataMember(Order = 0)]
public string CarId { get; set; }
[DataMember(Order = 1)]
[Required(Patch = True, Put = False]
public string IsIncluded { get; set; }
}
Then my ModelState will take the very into account.
I thought about creating separate derived classes for each action (verb), but the code quickly becomes incredibly verbose.
This is one of the drawbacks of using data annotations for validation unfortunately they cannot be conditionally added.
There are a number of options to you...
Create separate models (or view models) for each verb.
Look into something like this.. http://andrewtwest.com/2011/01/10/conditional-validation-with-data-annotations-in-asp-net-mvc/ which extends required to be IfRequired and adds conditional validation to data annotations. (You would need to roll your own I should think and it may get clumsy!)
Try something like FluentValidation.
http://fluentvalidation.codeplex.com/ (this could be a good option depending on your application requirements).
Hope this helps!
Related
I use some Azure Functions as WebApi. Now I have the following DTO to create a vehicle:
public class CreateVehicleDto
{
public string LicensePlate { get; set; }
public int? Mileage { get; set; }
public string Manufacturer { get; set; }
}
My method header looks like this:
[FunctionName("CreateVehicle")]
public async Task<ActionResult<ReadVehicleDto>> CreateVehicle([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "vehicles")] CreateVehicleDto createVehicleDto){}
The problem is that when my client sends a mileage higher than int.MaxValue all properties of the DTO are null and the method runs without throwing any exception whatsoever.
Is there a way to handle that? In case of a too high mileage I want to return a BadRequestResult.
I've also tried to use the System.ComponentModel.DataAnnotations to set a maximum like this [Range(0, int.MaxValue)] and validate it with the System.ComponentModel.DataAnnotations.Validator. But when the object gets validated it's already too late because all properties of the DTO get passed into the method with a value of null.
I have created an WebAPI web app and I would like to validate the data when POST and based on the results to call an external API.
The data will be saved in the database as it is, apart from the validation results.
Validation will be done only for calling the external API.
I have created the logic for posting to the external API but I'm not quite sure how it will be the optimal way to validate the data.
My model includes 10 classes like the below Class1 with multiple properties and I've created a controller for each of them.
The properties can have the true/false values but as strings.
public class Class1
{
public ICollection<Class1Data> Data { get; set; }
}
public class Class1Data
{
public int Id { get; set; }
public string Prop1{ get; set; }
public string Prop2 { get; set; }
..
public string Prop10 { get; set; }
}
WebAPI contoller for POST:
[ResponseType(typeof(Class1))]
public async Task<IHttpActionResult> PostClass1(Class1 class1)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Class1.Add(class1);
await db.SaveChangesAsync();
return CreatedAtRoute("DefaultApi", new { id = Class1.Id }, class1);
}
I've manage somehow to validate one property and POST to external API but not quite sure how I can do that for all my model classes ( I have around 10, 20 props each ).
var notValid = Class1.Data.Where(x => x.Prop1 == "False");
if (notValid != null)
{
foreach ( var fault in notValid )
{
// Call external API using fault.Prop1 / fault.Prop5 / ..
}
}
How could I achieve this?
I hope that my question makes any sense to you.
The simplest way is to use Data Annotations:
Examples:
[StringLength(100)]
public string AccountKey { get; set; }
[Required]
[StringLength(100)]
public string FirstName { get; set; }
Or if you need custom validations you can define them as Custom Validation Attributes and use them like below:
[Required]
[CountryCode]
[StringLength(3)]
public string CountryCode { get; set; }
In this sample [CountryCode] is a Custom validation which you can implement like this:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class CountryCodeAttribute : RegularExpressionAttribute
{
public CountryCodeAttribute() :
base("^[A-z]{2,3}([-]{1}[A-z]{2,})?([-]?[A-z]{2})?$")
{
ErrorMessage = "Invalid country code.";
}
}
You will need to import this namespace for this kind of validation:
System.ComponentModel.DataAnnotations
I do not want do bind the Id property on my CustomerViewModel so I added a [BindNever] attribute but it is not working. What could be the solution?
I have the following:
CustomerController.cs
// PUT api/customers/5
[HttpPut("{id}")]
public async Task<IActionResult> Put([FromUri] int id, [FromBody]CustomerViewModel customer)
{
//Implementation
}
CustomerViewModel
public class CustomerViewModel
{
[BindNever]
public int Id { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Email { get; set; }
}
If I input the following json . The id property still gets binded
{
"id": 100,
"lastName": "Bruce",
"firstName": "Wayne",
"email": "bruce#gothamcity.com"
}
This Blog post is an interesting read and concludes that the [FromBody] annotation "overrides" the BindBehaviourAttribute (BindNever is a simple specialization). The model is populated by all data available from the body (your JSON data in this case).
I do not consider this as intuitive, and the issue has a nice statement about this:
[BindRequired] customizes the MVC model binding system . That's its
purpose and it's working as designed.
[FromBody] switches the affected property or parameter into the
different world of input formatting. Each input formatter (e.g.
Json.NET and a small MVC-specific wrapper) can be considered a
separate system with its own customization. The model binding system
has no knowledge the details of JSON (or any other) deserialization.
Lesson learned: BindNever does not work in this scenario.
What are alternatives ?
Solution 1: Writing some custom model binding code. I have not done it myself, but What is the correct way to create custom model binders in MVC6? may help.
Solution 2: Rather pragmatic one
Perhaps this simple (but not very nice) workaround helps you out:
[HttpPut("{id}")]
public async Task<IActionResult> Put([FromUri] int id, [FromBody]CustomerViewModel customer)
{
customer.Id = 0;
//Implementation
}
also you could do this
public class CustomerViewModel
{
public int Id { get; private set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Email { get; set; }
}
I add a note.
Now it's officially explained by Microsoft.
https://learn.microsoft.com/ja-jp/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0#attributes-for-complex-type-targets
https://learn.microsoft.com/ja-jp/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0#input-formatters
https://learn.microsoft.com/ja-jp/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0#frombody-attribute
In summary,
If we use the “FromBody attribute (including defaults such as HttpPost attribute)”, it depends on the input formatter and the BindNever attribute etc. will not work.
Instead, we can do so by specifying the attribute that corresponds to the input formatter.
For example, for the default json
It can be ignored using "System.Text.Json.Serialization.JsonIgnoreAttribute".
Try NotMapped attribute.
Body must be at least 30 characters; you entered 24.
I'm having some routing problems with OData v3, composite keys and deleting items. I've set up my controller and entities as below (stubbed the methods here, they're complete in my implementation) and can run basic queries on the data (filtering etc. for GET)
When I call the url http://localhost:62658/OData/ProductStockLimit(StockLimitGroupId=1,ProductRegexMatch=Test) with DELETE however I keep getting 404's with the message "No HTTP resource was found that matches the request URI"
I assume the routing isn't picking up this method but I have no idea why as all of my other OData routes are working correctly with deletes, the only difference I can see is that this is a composite key one.
Anyone else had this problem?
public class ProductStockLimit
{
[Key, Column(Order = 2)]
public string ProductRegexMatch { get; set; }
[Key, ForeignKey("StockLimitGroup"), Column(Order = 1)]
public int StockLimitGroupId { get; set; }
public virtual StockLimitGroup StockLimitGroup { get; set; }
[Column(Order = 3)]
public double Quantity { get; set; }
}
namespace Website.Areas.OData.Controllers
{
public class ProductStockLimitController : ODataController
{
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<ProductStockLimit> Get()
{
}
public IHttpActionResult Post(ProductStockLimit item)
{
}
public HttpResponseMessage Delete( [FromODataUri]int StockLimitGroupId,[FromODataUri] string ProductRegexMatch)
{
}
}
}
From what I've looked at, it seems the OData v3 implementation doesn't handle composite keys properly. This link has a routing convention class which when applied handles them correctly.
Quick word of caution don't use the parameter name "key" for your action method as this will cause it to try and add another "key" element in the dictionary causing an exception.
What are good strategies for rebuilding/enriching a nested or complex ViewModel?
A common way to rebuild a flat ViewModel is shown here
But building and rebuilding a nested ViewModel using that method is too complex.
Models
public class PersonInfo
{
public int Id { get; set; }
public string Name { get; set; }
public int Nationality { get; set; }
public List<Address> Addresses { get; set; }
}
public class Address
{
public int AddressTypeID { get; set; }
public string Country { get; set; }
public string PostalCode { get; set; }
}
public class AddressType
{
public int Id { get; set; }
public string Description { get; set; }
}
view models
public class PersonEditModel
{
public int Id { get; set; }
public string Name { get; set; } //read-only
public int Nationality { get; set; }
public List<AddressEditModel> Addresses { get; set; }
public List<SelectListItem> NationalitySelectList { get; set; } //read-only
}
public class AddressEditModel
{
public int AddressTypeId { get; set; }
public string AddressDescription { get; set; } //read-only
public string Country { get; set; }
public string PostalCode { get; set; }
public List<SelectListItem> CountrySelectList { get; set; } //read-only
}
actions
public ActionResult Update(int id)
{
var addressTypes = service.GetAddressTypes();
var person = service.GetPerson(id);
var personEditModel= Map<PersonEditModel>.From(person);
foreach(var addressType in addressTypes)
{
var address = person.Addresses.SingleOrDefault(i => i.AddressTypeId == addressType.Id)
if(address == null)
{
personEditModel.Addresses.Add(new AddressEditModel
{
AddressTypeId = addressType.Id
});
}
else
{
personEditModel.Addresses.Add(Map<AddressEditModel>.From(address));
}
}
EnrichViewModel(personEditModel, person, addressTypes); //populate read-only data such as SelectList
return Index(personEditModel);
}
[HttpPost]
public ActionResult Update(PersonEditModel editModel)
{
if(!ModelState.IsValid)
{
var person = service.GetPerson(editModel.Id);
var addressTypes = service.GetAddressTypes();
EnrichViewModel(editModel, person, addressTypes);
return View(editModel);
}
service.Save(...);
return RedirectToAction("Index");
}
//populate read-only data such as SelectList
private void EnrichViewModel(PersonEditModel personEditModel, Person person, IEnumerable<AddressType> addressTypes)
{
personEditModel.Name = person.Name;
personEditModel.NationalitySelectList = GetNationalitySelectList();
foreach(var addressEditModel in personEditModel.Addresses)
{
addressEditModel.Description = addressTypes.Where(i => i.Id = addressEditModel.AddressTypeId).Select(i => i.Description).FirstOrDefault();
addressEditModel.CountrySelectListItems = GetCountrySelectList(addressEditModel.AddressTypeId);
}
}
My code for building and rebuilding the ViewModels (PersonEditModel and AddressEditModel) is too ugly. How do I restructure my code to clean this mess?
One easy way is to always build a new view model instead of merging/rebuilding since MVC will overwrite the fields with the values in ModelState anyway
[HttpPost]
public ActionResult Update(PersonEditModel editModel)
{
if(!ModelState.IsValid)
{
var newEditModel = BuildPersonEditModel(editModel.Id);
return View(newEditModel);
}
but I'm not sure that this is a good idea. Is it? Are there other solutions besides AJAX?
I'm going to tackle your specific pain points one-by-one and I'll try to present my own experience and likely solutions along the way. I'm afraid there is no best answer here. You just have to pick the lesser of the evils.
Rebuilding Dropdownlists
They are a bitch! There is no escaping rebuilding them when you re-render the page. While HTML Forms are good at remembering the selected index (and they will happily restore it for you), you have to rebuild them. If you don't want to rebuild them, switch to Ajax.
Rebuilding Rest of View Model (even nested)
HTML forms are good at rebuilding the whole model for you, as long as you stick to inputs and hidden fields and other form elements (selects, textarea, etc).
There is no avoiding posting back the data if you don't want to rebuild them, but in this case you need to ask yourself - which one is more efficient - posting back few extra bytes or making another query to fetch the missing pieces?
If you don't want to post back the readonly fields, but still want the model binder to work, you can exclude the properties via [Bind(Exclude="Name,SomeOtherProperty")] on the view model class. In this case, you probably need to set them again before sending them back to browser.
// excluding specific props. note that you can also "Include" instead of "Exclude".
[Bind(Exclude="Name,NationalitySelectList")]
public class PersonEditModel
{
...
If you exclude those properties, you don't have to resort to hidden fields and posting them back - as the model binder will simply ignore them and you still will get the values you need populated back.
Personally, I use Edit Models which contain just post-able data instead of Bind magic. Apart from avoiding magic string like you need with Bind, they give me the benefits of strong typing and a clearer intent. I use my own mapper classes to do the mapping but you can use something like Automapper to manage the mapping for you as well.
Another idea may be to cache the initial ViewModel in Session till a successful POST is made. That way, you do not have to rebuild it from grounds up. You just merge the initial one with the submitted one in case of validation errors.
I fight these same battles every time I work with Forms and finally, I've started to just suck it up and go fully AJAX for anything that's not a simple name-value collection type form. Besides being headache free, it also leads to better UX.
P.S. The link you posted is essentially doing the same thing that you're doing - just that its using a mapper framework to map properties between domain and view model.