In my ASP.NET Core 2.1 MVC application, I want to expose a route like this:
/address/v1/postcode/{postcode}/street/{street}
I have defined my controller like this:
[Route("address/v1")]
[ApiController]
public class StreetController : ControllerBase
{
[HttpGet("postcode/{postcode}/street/{street}")]
public ActionResult<GetStreetDetailsResponse> GetStreetDetails([FromRoute] GetStreetDetailsRequest request)
{
throw new NotImplementedException();
}
}
public class GetStreetDetailsRequest
{
[Required]
[StringLength(4, MinimumLength = 4)]
[RegularExpression("^[\\d]+$")]
public string Postcode { get; set; }
[Required]
public string Street { get; set; }
}
public class GetStreetDetailsResponse
{
}
The route resolves as expected, however, the framework is not deserializing the Postcode and Street values, and those properties are not populated correctly in GetStreetDetailsRequest.
For example, if I call:
http://localhost/address/v1/postcode/0629/street/whatever
when it gets into the action method, the value of request.Postcode="{postcode}" and request.Street="{street}".
The issue appears to be due to the casing of my property names, because if I change GetStreetDetailsRequest to:
public class GetStreetDetailsRequest
{
[Required]
[StringLength(4, MinimumLength = 4)]
[RegularExpression("^[\\d]+$")]
public string postcode { get; set; }
[Required]
public string street { get; set; }
}
everything works correctly. However, I’m not happy with that solution as it doesn't follow conventional C# naming standards.
I’ve tried decorating the properties with [DataMember(Name="postcode")] or [JsonProperty("postcode")], but these seem to be ignored too.
For the record, in my Startup.ConfigureServices() method I am using the default serializer, which I understand supports camel case:
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Has anyone got a solution that will enable me to expose the route with camel case properties using Pascal case in the request object property names?
Well, you are correct in some way. This:
[HttpGet("postcode/{postcode}/street/{street}")]
Says you have a postcode and a street property, and you have none of them. If you want the default binding to work, the casing must match exactly:
[HttpGet("postcode/{Postcode}/street/{Street}")]
Related
I have checked for possible solutions to this and haven't been able to get any meaningful solution. I am working on an ASP.NET Web API project and I have a controller route with the below method signature:
[HttpPost("cimgAirtimeVending/{msisdn}")]
public async Task<ActionResult<ResponseObject>> CIMGAirtimeVending([FromBody] CIMGBillPaymentRequest _cimgAirtimeBillPaymentRequest, [Required, RegularExpression(#"\d{13}")] string msisdn)
CIMGBillPaymentRequest is a DTO with validations defined on each property as follows:
public class CIMGBillPaymentRequest
{
[Required, RegularExpression(#"\w+"), StringLength(30)]
public string RequestId { get; set; }
[Required, StringLength(10, MinimumLength = 10), RegularExpression(#"\d+")]
public string DebitAccount { get; set; }
/* [Required, RegularExpression(#"\w+")]
public string Narration { get; set; } */
[Required]
public bool IsFees { get; set; }
public List<Charge> Charges { get; set; }
[Required, RegularExpression(#"[\w-]+")]
public string ProductId { get; set; }
[Required, Range(1, 100, ErrorMessage = "Enter valid ChannelId")]
public int ChannelId { get; set; }
// [Required, Range(100, 100000, ErrorMessage = "Enter valid Amount (>=100)")]
public decimal Amount { get; set; }
[Required, RegularExpression(#"\w+")]
public string CustomerReference { get; set; }
}
public class Charge
{
// [StringLength(10, MinimumLength = 10), RegularExpression(#"\d{10,}")]
[RegularExpression(#"\w+")]
public string Account { get; set; }
public decimal Fee { get; set; }
}
The strange thing is, it appears not all the validation works. If isFees is removed from the DTO, it should return a Required validation error, but that does not happen. The validation is completely ignored. This does not happen with the ProductId property. A Required validation error is returned if it is not included in the DTO
Another issue I had was with the Account property in the Charge class. If you look at the line commented above it, you will see I am using a Regex validation RegularExpression(#"\d{10,}")] i.e. to ensure that it is a string of at least 10 digits. However, this validation is completely ignored and I had to use [RegularExpression(#"\w+")].
Any idea what the issue is and possible solution?
Take a look at the documentation here.
"The RequiredAttribute attribute specifies that when a field on a form is validated, the field must contain a value. A validation exception is raised if the property is null, contains an empty string (""), or contains only white-space characters."
"If the MVC data model or entity partial class contains a field that is annotated with the RequiredAttribute attribute, but the page does not contain the property, an error is not raised. Validation occurs only for fields that are submitted to the server."
If you leave out the property entirely then the required validation will not trigger. It will only trigger when explicitly called with null, empty string or string with only whitespace. Therefore I'm assuming the required check on some other fields namely the struct fields also don't work, however when you then check for a range, like with the ChannelId property it will validate the range. The issue here is that a struct can never be null. This means the RequiredAttribute won't work on bool, int, decimal, etc. fields.
In this case when not providing anything for IsFees will set the property with the value default(bool) the default value for a bool is false.
As for the Account field, look at the documentation here
"You apply the RegularExpressionAttribute attribute to a property when you need to validate values for the property against a regular expression. The regular expression enables you to specify very precisely the format of valid values. The Pattern property contains the regular expression. If the value of the property is null or an empty string (""), the value automatically passes validation for the RegularExpressionAttribute attribute. To validate that the value is not null or an empty string, use the RequiredAttribute attribute."
When providing null, or an empty string to a field with RegularExpressionAttribute it will pass the check. This means, for your Account field you need to use [Required, RegularExpression(#"\d{10,}")].
I am working with a bunch of JSON files that I need to deserialize . A proper I am facing is that some of the Content in the JSON files do not follow proper coding standards , hence when I create the POCO classes I am violating naming conventions
Below is a small example
{
"category": "classified"
}
In this case I would have to create a POCO class as
public class Category
{
public string category{ get; set; }
}
here I am starting a property name in a simple letter which is bad naming convention in C#.
Anything I can do about that ?
Use JsonProperty attribute to specify the property name:
using Newtonsoft.Json;
// ...
[JsonProperty(PropertyName = "category")]
public string Category{ get; set; }
If you are using newer versions of Newtonsoft's serializer it will handle most simple things for you, like this. More complex ones can be handled with an attribute.
public class Category
{
[JsonProperty(PropertyName = "category")]
public string DifferentNameCompletely{ get; set; }
}
Is there any data annotation for the allowed values in ASP.NET MVC Core? Since there is no enum in SQL server I am not able to migrate my class with enum field in it to the database. I want to give possible/allowed values to the field in the class. Is there any way to do this?
public class Employee
{
[Key]
public int ID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Status { get; set; }
}
I want to provide Active and Inactive as the only possible values to the Status field.
you can also do this using a regular expression as below:
[Required]
[RegularExpression("Active|Inactive", ErrorMessage = "Invalid Status")]
public string Status { get; set; }
More details can by found here
As #ps2goat mentioned, you could use a check constraint on your database. However, for the model coming into the API you probably still want to provide validation there. Ideally you will do what you can, within reason, to prevent bad data from ever getting to the data layer. You don't mention whether you're using an n-tier architecture, or if your controller is directly referencing the data model. Either way, I believe this custom attribute can be used either at the API layer or on the entity model.
This is a good answer that explains how to create a custom validation attribute. It's an old answer, but it still applies to .Net Core. And here is an answer for a custom validation attribute in .Net Core. It basically looks like this:
public class EmployeeStatusAttribute : ValidationAttribute
{
private string[] _allowedValues;
public EmployeeStatusAttribute(string[] allowedValues)
{
_allowedValues = allowedValues;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var employee = value as Employee;
if (_allowedValues.Contains(employee.Status))
{
return ValidationResult.Success;
}
return new ValidationResult(`{employee.Status} is not a valid status`);
}
}
Then in your model:
public class Employee
{
...
[EmployeeStatus("Active", "Inactive")]
public string Status { get; set; }
...
}
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 am very new in dynamodb. I am following http://www.rkconsulting.com/blog/persistence-model-framework-with-aws-dynamodb
step by step tutorial for connecting and CRUD operation in dynamodb and it`s works fine.
In that tutorial they using attribute mapping for map class properties
[DynamoDBTable("Dinosaur")]
public class Dinosaur
{
[DynamoDBHashKey]
public string Id { get; set; }
[DynamoDBProperty(AttributeName = "Name")]
public string Name { get; set; }
[DynamoDBProperty(AttributeName = "HeightMetres")]
public double HeightMetres { get; set; }
[DynamoDBProperty(AttributeName = "WeightKG")]
public double WeightKg { get; set; }
[DynamoDBProperty(AttributeName = "Age")]
public int Age { get; set; }
[DynamoDBProperty(AttributeName = "Characteristics")]
public List<string> Characteristics { get; set; }
[DynamoDBProperty(AttributeName = "Photo", Converter = typeof(ImageConverter))]
public Image Photo { get; set; }
[DynamoDBIgnore]
public int IgnoreMe { get; set; }
}
My question is there any way to map class properties without using attribute ?
like as mongoDb
public class Employee
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
}
we can write this in this way in a separate class
BsonClassMap.RegisterClassMap<Employee>(cm => {
cm.AutoMap();
cm.IdMemberMap.SetRepresentation(BsonType.ObjectId);
});
Is it possible in dynamodb ?
In the latest version of the .NET SDK you don't have to put in the attribute tags, it will see all read/write properties and upload the attributes as the same name. You would only have to use the [DynamoDBProperty(...)] if you want the attribute name in DynamoDB to be something other than the .NET object name.
So in your case you could simply remove that attribute for all properties except photo (which needs the converter, you could remove the AttributeName part of it) and WeightKg (because the capitalization is different) and you would get the same result.
I see this is a little bit older question now, so it may not have been that way in older versions (not sure) but I'm using 3.3.0.0 of the SDK and it does work that way. You have probably moved on but answering for others that may come upon this thread as I did...
There is no way, the default "strongly typed" client relies on attributes.
If you have time to do the plumbing yourself - there is nothing stopping your from doing your own implementation of the POC to Dynamo mapping though. Amazon client api (AWSSDK.DynamoDBv2) exposes the raw class AmazonDynamoDBClient which handles all the API calls and the DynamoDBConext is just implementation of IDynamoDBContext interface - which exposes all the "strongly typed" operations. So you can make your own implementation and take different mapping approach in it.
Also you can make a feature request for this:
https://github.com/aws/aws-sdk-net/issues