Different validation contexts in ASP.Net and how to handle this - c#

I have two endpoints in my ASP.net backend. One that takes an object with optional null parameters i.e. not required and also without any range checking so that this object can be stored in the database and a second that takes an ID of an object in the database. In this second endpoint I want to get the object out of the database and perform further validation. I.e. the user needs to have set all the optional values to be valid in range values for this second endpoint to work.
Basically I have two different endpoints that have the same data object but different validation needs.
eg
public class AnObject
{
[Required]
public Guid Id {get;set;}
public double? Foo {get;set;}
public double? Bar {get;set;}
}
with endpoint like this
[HttpPost]
public IActionResult Endpoint1([FromBody]AnObject anObject)
{
// if anObject is not valid (id is missing or incorrect types) ASP.net returns a BadRequest
// if ok add anObject to DB with id as key
}
and then a second object like this that has the same fields but non nullable and Required with Range checking.
public class AMoreStringentObject
{
[Required]
public Guid Id {get;set;}
[Required][Range(1,10)]
public double Foo {get;set;}
[Required][Range(1,10)]
public double Bar {get;set;}
}
and endpoint
[HttpPost]
public IActionResult Endpoint2(string id)
{
// get the AnObject from the DB with id matching parameter id
// now want to validate this using the AMoreStringentObject
}
in the second end point I would like get the AnObject from the database and perform validation using the DataAnnotations library. The second object and the first are not the same (nullable type on first) so I can't simply automap them. So i thought what I could do is serialize the AnObject to json and not write out things that are null, easy enough. So I want to then call the validation that must occur somewhere in the asp.net magic that would throw out error messages like "Foo is required". But how do I do that? i.e. validate the json string using the AMoreStringentObject so that the Required attributes throw a BadRequest with the required error. OR if there is a better way to go about this I'm all ears.
Additionally, I'm aware that I could do this with settings in the json deserializer but you get an unhelpful exception when you do this. I'd really like to get the BadRequest response that the Required field creates as this is a much more helpful message to the user. eg the json exception has a json position which is no use to the user because it is the backend that has serialized it.

I would recommend looking into FluentValidation which can be integrated into ASP.NET Core pipeline and will allow you to build reusable validations.
But if you want to use the build into the framework validation - you can look into Validator.TryValidateObject with manually created ValidationContext:
AMoreStringentObject toValidate = ...;
var vc = new ValidationContext(toValidate, serviceProvider: null, items: null);
var results = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(u, vc, results, true);

Related

How to save/pass MongoDB UpdateDefinition for logging and later use?

I am stumped on how to save/pass MongoDB UpdateDefinition for logging and later use
I have created general functions for MongoDB in Azure use on a collection for get, insert, delete, update that work well.
The purpose is to be able to have a standard, pre-configured way to interact with the collection. For update especially, the goal is to be able to flexibly pass in an appropriate UpdateDefinition where that business logic is done elsewhere and passed in.
I can create/update/set/combine the UpdateDefinition itself, but when i try to log it by serializing it, it shows null:
JsonConvert.SerializeObject(updateDef)
When I try to log it, save it to another a class or pass it to another function it displays null:
public class Account
{
[BsonElement("AccountId")]
public int AccountId { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
}
var updateBuilder = Builders<Account>.Update;
var updates = new List<UpdateDefinition<Account>>();
//just using one update here for brevity - purpose is there could be 1:many depending on fields updated
updates.Add(updateBuilder.Set(a => a.Email, email));
//Once all the logic and field update determinations are made
var updateDef = updateBuilder.Combine(updates);
//The updateDef does not serialize to string, it displays null when logging.
_logger.LogInformation("{0} - Update Definition: {1}", actionName, JsonConvert.SerializeObject(updateDef));
//Class Created for passing the Account Update Information for Use by update function
public class AccountUpdateInfo
{
[BsonElement("AccountId")]
public int AccountId { get; set; }
[BsonElement("Update")]
public UpdateDefinition<Account> UpdateDef { get; set; }
}
var acct = new AccountUpdateInfo();
acctInfo.UpdateDef = updateDef
//This also logs a null value for the Update Definition field when the class is serialized.
_logger.LogInformation("{0} - AccountUpdateInfo: {1}", actionName, JsonConvert.SerializeObject(acct));
Any thoughts or ideas on what is happening? I am stumped on why I cannot serialize for logging or pass the value in a class around like I would expect
give this a try:
var json = updateDef.Render(
BsonSerializer.SerializerRegistry.GetSerializer<Account>(),
BsonSerializer.SerializerRegistry)
.AsBsonDocument
.ToString();
and to turn a json string back to an update definition (using implicit operator), you can do:
UpdateDefinition<Account> updateDef = json;
this is off the top of my head and untested. the only thing i'm unsure of (without an IDE) is the .Document.ToString() part above.

Options Pattern in ASP.NET Core - how to return sub-options as a JSON string (not strongly typed)

The following documentation illustrates how to use the Options Pattern in ASP.NET Core to create a strongly-typed options class to access JSON configuration data.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options
This C# class
public class MyOptions
{
public string Option1 { get; set; }
public int Option2 { get; set; }
}
represents a portion of this JSON configuration file (the first two root-level properties)
{
"option1": "value1_from_json",
"option2": -1,
"subOptions": {
"subOption1": "subvalue1_from_json",
"subOption2": 200
}
}
I want to add another C# property named SubOptions to the MyOptions class that returns the raw data of the subOptions JSON sub-section, without creating a strongly-typed class for that sub-section of the JSON configuration file, but I don't know what data type to use (or if it's even possible to do that).
If I use string, I get a runtime error when service.Configure<MyOptions>(Configuration); is called, saying System.InvalidOperationException: 'Cannot create instance of type 'System.String' because it is missing a public parameterless constructor.
If I use object or dynamic, I get a different runtime error when service.AddSingleton(cfg => cfg.GetService<IOptions<MyOptions>>().Value); is called to register an instance of the MyOptions class, saying System.ArgumentNullException: 'Value cannot be null. Parameter name: type'
If I use JObject, I get {} back when I access the SubOptions property of the MyOptions object that's injected into my API Controller.
I know I can convert the sub-section to a JSON string property by escaping the sub-section data, but I want to avoid treating the sub-section as a string, and instead leave it as raw JSON.
Is it possible to do what I want to do? Is there a data type that works with the Options Pattern that will allow me to access the JSON sub-section without having to create a strongly-typed class?
*For background, I'm trying to create an API Controller method that returns the content of the JSON sub-section to the API client. I want to avoid using a strongly-typed class for the sub-section, so that the JSON configuration file can be edited on the server, adding new properties and values to the sub-section that will be returned to the API client, without having to update the C# code and redeploy the API service. In other words, I want the JSON sub-section to be 'dynamic', and just pull it and send it to the client. *
You can sorta do get raw configuration object by forcing your SubOptions property to be of IConfigurationSection:
public class MyOptions
{
public string Option1 { get; set; }
public int Option2 { get; set; }
public IConfigurationSection SubOptions { get; set; } // returns the "raw" section now
public string SubOptions_take2 { get; set; }
}
so you would still bind your strongly typed object in your Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration);
...
}
but this is where luck appears to run out, because even though it is a whole section - as far as options binder is concerned it's all been deserialised and parsed into hierarchy of values already. There appears to be no easy way to reassemble it back into one string. Injecting IOptionsMonitor allows you to get the values by opting for .GetChildren() but I could not find an obvious way to get the whole hierarchy without writing custom code to just recursively walk it (which I will leave out for you to play with should you feel this is worth the effort):
public IndexModel(IOptionsMonitor<MyOptions> options)
{
_options = options.CurrentValue;
var subOptions = _options.SubOptions as ConfigurationSection;
var children = subOptions.GetChildren(); // you see, the config has already been parsed into this hierarchy of items - it's too late to get the raw string value
var s = JsonConvert.SerializeObject(children);
// will produce something like this JSON:
//[{"Path":"SubOptions:subOption1","Key":"subOption1","Value":"subvalue1_from_json"},{"Path":"SubOptions:subOption2","Key":"subOption2","Value":"200"}]
}
one way around it will be to actually encode your json as string in the config file:
"subOptions_take2": "{\"subOption1\": \"subvalue1_from_json\",\"subOption2\": 200}"
then you can just grab it later:
public IndexModel(IOptionsMonitor<MyOptions> options)
{
_options = options.CurrentValue;
var subOptions_string = _options.SubOptions_take2;// this is valid json now: {"subOption1": "subvalue1_from_json","subOption2": 200}
}
I guess, you can use JObject from Newtonsoft.Json package - it's the default JSON parser & serializer in Asp.Net Core

parsing object with Property List<Claim> and tell the JsonConvert.DeserializeObject to use specific constructor for claims?

I'm using .NET Core with Newtonsoft.Json. I have a UserModel class that has a List<Claim> property
public class UserModel
{
public string GUID { get; set; }
public bool isActive { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public List<Claim> Claims { get; set; }
}
and I'm trying to parse the JSON request into this object class like so:
public IActionResult Testpost([FromBody]JObject body)
{
if (body == null) return BadRequest();
UserModel user = JsonConvert.DeserializeObject<UserModel>(body.ToString());
return Ok(user);
}
but deserializing JSON into an object like Claim class which I don't have access to throws an exception
Newtonsoft.Json.JsonSerializationException: 'Unable to find a constructor to use for type System.Security.Claims.Claim. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'Claims
because it is not able to decide on a constructor
According to online sources I can create a custom converter class that can manage the UserModel object creation but I would like to avoid this.
Is it possible to deserialize a JSON object into my UserModel class and tell the JsonConvert.DeserializeObject to use a specific Claim constructor like Claim(String, String) for parsing the Claims?
EDIT:
as mentioned by #PaulG i have already check the answer for How to programmatically choose a constructor during deserialization?
however the accepted solution used Creates a new class that implements the JsonConverter class then manually parses the body of the JObject request. Moreover, the answer shows how to deal with Claims but not with complex objects where claims are nested as properties
reading another solution in the thread it shows how to create a class that directly implements the constructor needed like so:
class MyClaim : Claim {
public MyClaim(string type, string value):
base(type, value){}
}
but this will require me to keep note on the difference between Claim and MyClaim when writing my code. the JSON converter may not be able to assume which constructor to use but i should be able to tell it which one. or is it by design and i have to suck it up and write extra code just for this?
because the alternative for me would be something like this:
public IActionResult CreatePublicUser([FromBody]JObject body)
{
string Username = body["Username"].ToString();
string Password = body["Password"].ToString();
var Claims = body["Claims"].Children();
List<Claim> UserClaims = new List<Claim>();
foreach (var c in Claims)
{
UserClaims.Add(
new Claim(
c["Type"].ToString(),
c["Value"].ToString()
)
);
}
UserModel NewUser = (new UserBuilder())
.WithUserName(Username)
.WithPassword(Password)
.WithClaims(UserClaims)
.Build();
return Ok(NewUser)
}
I suggest that you take a different approach altogether which is a common practice as well. Define a model only for the interaction with client, e.g.
public class UserModelWeb
{
public List<ClaimWeb> Claims { get; set; }
}
These DTO objects will be used only for the data conversion from JSON and never in the business logic layer. Then you can map your web models to the business logic that you will be using later. This will allow you to not be dependent on internal classes when you read external data. I.e. if you suddenly add a new field, it will be populated from external (and probably untrusted) source. This clear separation of concerns will not allow this since you will have to explicitly define a field in a web model.
Example for your case: let's say you will have later an internal field in the database that only you can edit: "InternalNote". If you add that field to the model, anyone can post the data and edit the field while your intention was only to allow yourself to edit it.
Additionally this will solve your problem since you won't need to cast to other classes.
P.S. You can use your class directly in action methods:
MyAction([FromBody]UserModelWeb user)
It should be deserialized from json right away.

How to pass two filled class models via an Angular http.post 'body' to the controller?

I am receiving an error message, "Sequence contains no elements" while trying to update a table in SQL from Angular 7 to an AspNet Core controller by passing two model parameters using an "http.post".
I am passing the data from the form to the class models with no problem because I can see the payload data in the browser console. However, when trying to pass the models as parameters in my api service to the controller, all of the parameters in the model are null. I usually don't have an issue when passing one model parm thru, but passing two of them to get to my controller with a [FromBody] doesn't seem to want to work for me.
I tried to wrap the models in curly brackets to pass them, to no avail:
UpdateService(serviceAddress: ServiceAddressModel, contact: ContactModel) {
let reqHeader = new HttpHeaders();
let body = { svc: serviceAddress, cnt: contact };
reqHeader.append('Content-Type', 'application/json');
return this.http.post(this.baseurl + 'api/customermanagement/update-service-address-info', body, { headers: reqHeader });
When I view the request / response in the browser console, I can see the data within the payload, so I know that the data is ready to pass.
My controller is set up as follows:
[Route("update-service-address-info")]
public bool UpdateServiceAddressAccount([FromBody] ServiceAddressEntity svc_id, [FromBody] ContactEntity cnt_id)
{
return serviceAddressService.UpdateServiceAddressAccount(svc_id, cnt_id);
}
Using breakpoints in this call shows null for all values.
If I can properly pass the parameters to my interface, I should be good-to-go. I am sensing that I am not structuring the parameters properly in the http.post body.
Your request body, { svc: serviceAddress, cnt: contact } is received as a json string, e.g. {"svc":{"serviceAddressProperty1":"value",...},"cnt":{"contactProperty1":"value",...}}. The parameters to your action method are bound via the default model binding mechanism (unless you provide your own custom model binding implementation). The default mechanism attempts to create instances by binding from the top level of the json object received with the request. enter code here
In simpler terms, lets assume you class ServiceAddressModel is defined like this:
public class ServiceAddressModel
{
public string Name { get; set; }
public string Property2 { get; set; }
}
the model binder looks for properties with the names "name" and "property2" at the top level of the json tree. If found, these are bound to the Name and Property2 properties of the created instance.
In your case, wrapping your models in a class that can make svc_id and cnt_id the top level properties would work fine. Like this example:
public class MyRequest
{
public ServiceAddressModel svc_id { get; set; }
public ContactEntity cnt_id { get; set; }
}
Then you can declare your action like
[Route("update-service-address-info")]
public bool UpdateServiceAddressAccount([FromBody] MyRequest request)
{
return serviceAddressService.UpdateServiceAddressAccount(request.svc_id, request.cnt_id);
}
Snake casing, camel casing should be allowed by default (you will have to try it, I havent tested that part). That is, if you declare your properties as SvcId and CntId (if you prefer more natural C# naming conventions) it should be able to bind correctly from JSONs with "svc_id" or "cnt_id".
Another option would be to implement custom model binders, but that might be a longer and more complex route.
Hope this helps.
Just try to pass the value like this and see
let body = { svc_id: serviceAddress, cnt_id: contact };

How do I enforce e.g. a numeric constraint using DataAnnotations?

I'm trying to utilize model state to validate requests to my WebAPI, but I'm having some trouble with how to control the error messages for some properties. For example, given the following model
public class Stuff {
[Range(0, Double.PositiveInfinity)]
public double? SomeProp { get; set; }
}
(and some infrastructure defined at the bottom of the post) a request with a payload such as { someProp: "1.2" } will model bind correctly - and negative or missing values will give errors - but if I enter something that isn't a valid number (e.g. { someProp: "hello" }) the model state has an error such as
Error converting value "hello" to type 'System.Nullable`1[System.Double]'. Path 'someStuff', line 1, position 41.
(I guess the position is in the JSON object, here cut from a larger request, so don't mind the exact numbers...). For another property, which isn't nullable but instead has a [Required] attribute, the error message about a missing required property is included in the model state dict in addition to the above message.
I do understand why "hello" cannot be bound to a double? (or double), but regardless of constraints I set in attributes, it seems that the value has to be model bound before they are even checked, and if model binding fails I both get that not-so-friendly message above, as well as all other errors that apply when the value is unset. The only constraint I've been able to apply before attempting to bind the value is a Regex, but if that fails I get a similarly user-unfriendly message like
The field SomeProp must match the regular expression '<my regex>'.
Is there a way to work around this, so that I can give the user a better error message when entering something non-numeric into a textbox that should specify a numeric value?
Footnotes: my validation infrastructure
[HttpPost]
[Validate]
public IHttpActionResult Validate(Stuff stuff) {
return Ok();
}
where the Validate attribute is defined like this:
public class ValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request
.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}

Categories