I'm using NancyFx model binding through the routing url and am trying to set up some validation for required properties. Class is as follows:
public class Query
{
[Required]
public string ClientId { get; set; }
public List<string> Customers { get; set; }
}
My route is as follows:
Get["/test?customers=c1,c2"] = args =>
{
var query = new Query(); // A
try
{
query = this.Bind<Query>(); // B
}
catch (ModelBindingException ex)
{
throw ex;
}
return db.Execute(Query);
};
}
At A, I expected there to be some sort of exception since ClientId is required but it is null when a new Query is initialized, but there is nothing.
Failing that, at B, I expected there to be some sort of error when I try to bind the Query object. Looking att he debugger, the query object's Customers property correctly has the expected value of "c1,c2". However, the ClientId in the query object is null, and there is no error. Am wondering what I can do to trigger an exception based on the fact that ClientId is required.
Check Nancy Wiki on validation
Related
I created a .NET Core API project as below. Everything works very well. But when I send a small JSON file, the null fields in the DTO are reflected in the database. So how can I update only the submitted fields?
When I send only the name field in the JSON object, it updates all the other fields, how can I do this in SaveChangesAsync in the DataContext?
When I send only the name field in the JSON object, it records all the fields as null. How can I prevent this? In other words, only the sent data should be updated, and the others should be recorded with their original values. Is there any way I can achieve this within the dbcontext object?
I am sending a JSON like here, but because the description field is empty inside the JSON object, it is changed to null in the database.
{
"id": 2,
"name": "test"
}
CompanyController, I am sending the JSON object via the body:
[HttpPut]
public async Task<IActionResult> Update([FromBody] CompanyUpdateDto updateCompany)
{
await _service.UpdateAsync(_mapper.Map<Company>(updateCompany));
return CreateActionResult(CustomResponseDto<CompanyUpdateDto>.Success(204));
}
I am sending my updatedDto object, sometimes name, and description fields, sometimes just the name field.
public class CompanyUpdateDto
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public DateTime? UpdatedDate { get; set; }
}
CompanyModel:
public class Company
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public DateTime? CreatedDate { get; set; }
public DateTime? UpdatedDate { get; set; }
}
DataContext:
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
foreach (var item in ChangeTracker.Entries())
{
if (item.Entity is BaseEntity entityReference)
{
switch (item.State)
{
case EntityState.Added:
{
entityReference.CreatedDate = DateTime.UtcNow;
break;
}
case EntityState.Modified:
{
Entry(entityReference).Property(x => x.CreatedDate).IsModified = false;
break;
}
}
}
}
return base.SaveChangesAsync(cancellationToken);
}
With AutoMapper, you can define a rule that only map from the source member to the destination member if the source member is not null via .Condition().
You may refer to the example in here.
CreateMap<CompanyUpdateDto, Company>()
.ForAllMembers(opt => opt.Condition((src, dest, value) => value != null));
Demo # .NET Fiddle
A concern is that you need to fetch the existing entity and map it with the received object to be updated as below:
[HttpPut]
public async Task<IActionResult> Update([FromBody] CompanyUpdateDto updateCompany)
{
// Get existing entity by id (Example)
var _company = await _service.GetAsync(updateCompany.Id);
// Map source to destination
_mapper.Map<CompanyUpdateDto, Company>(updateCompany, _company);
await _service.UpdateAsync(_company);
return CreateActionResult(CustomResponseDto<CompanyUpdateDto>.Success(204));
}
You can also ignore null values during serialization:
var company = new CompanyUpdateDto();
company.Description = "New description";
JsonSerializerOptions options = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
var serialized = JsonSerializer.Serialize(company,options);
You will have to make design decisions here for your API update operation.
Get and Put the objects in full.
When retrieving an object, your Get operation must return the object in full detail. Then, when any fields change, the client will send the object back in full to the Put endpoint. In this way, you will keep the values for all fields.
However, in some cases, you only want to expose a subset of the fields and leave some of the fields untouched or updated by the system. In those cases, you will have to retrieve the object from the database by some identifier and then assign the fields from the incoming object.
Use JSON Patch
You will have a Patch endpoint for the resource. In the request body, you specify what operation for the object and which field has changed. When receiving the request, you will apply the changes based on the operation and fields in the request body.
The downside for the second option is that your client must follow the JSON Patch standards.
I am creating a customer model for my API and I've set my 'Name' and 'Email' fields as required. But when we leave those fields empty while creating a new customer, we got a built-in error from EF core , instead I want to show my own error message...how can I do that.
I've tried to add validations by code in my post method but It doesn't work...can anyone help me with that?...Thanks in advance
If you want to do simple validation: Fluent Validation
public class UserRegisterValidator : AbstractValidator<User>
{
public UserRegisterValidator()
{
RuleFor(r => r.email).NotEmpty().WithMessage("Email address cannot be empty.");
.
.
.
}
}
When adding a new user
UserRegisterValidator v = new UserRegisterValidator();
ValidationResult result = v.Validate(usr);
if (!result.IsValid) return StatusCode(StatusCodes.Status400BadRequest, result.Errors); //Returns the specified error message
if (result.IsValid)
{
//If there is no error, the necessary actions are here
}
If you want to use server side validation you can specify the error message in validation tags. For example:
public class Movie
{
public int ID { get; set; }
[Required(ErrorMessage = "Name is required")]
public string Name{ get; set; }
}
Then you can also add some DetailsProvider to get better details when calling the api:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.ModelMetadataDetailsProviders.Add(new NewtonsoftJsonValidationMetadataProvider());
}).AddNewtonsoftJson();
Validation in front end has some other scenarios. Take a look at
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-7.0
Tools
Visual Studio 2017
ASP.NET Core 2.2
Postman v7.2.0
What I'm trying to do
Send FormData from Postman to an ASP.NET Core controller and have the data from the request bind to to a command class that has properties with private setters.
I've sent JSON data using the same setup (private setters) with no problem. The FromBody attribute deserialises the JSON string to the model without errors.
The Problem
Properties that are primitive types do not bind if the model has a private setter. However, complex types do regardless of the access modifier.
Controller
[HttpPost]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> CreateItemAsync([FromForm]CreateItemCommand command)
{
bool result = false;
commandResult = await _mediator.Send(command);
if (!commandResult)
{
return BadRequest();
}
return Ok();
}
Command
Note: The Title property has been left with a public setter deliberately to illustrate the behviour
[DataContract]
public class CreateItemCommand
:IRequest<bool>
{
[DataMember]
public string Title { get; set; }
[DataMember]
public string Description { get; private set; }
[DataMember]
public int Count { get; private set; }
[DataMember]
public HashSet<string> Tags { get; private set; }
[DataMember]
public string ItemDate { get; private set; }
[DataMember]
public List<IFormFile> Documents { get; private set; }
public CreateItemCommand()
{
Skills = new HashSet<string>();
Systems = new HashSet<string>();
}
public CreateItemCommand(string title, string description,
int count, HashSet<string> tags, string itemDate,
List<IFormFile> documents)
: this()
{
Title = title;
Description = description;
Count = count
Tags = tags;
ItemDate = itemDate;
Documents = documents;
}
}
In Postman I now setup the request as follows:
I've had to obfuscate some of the information, but you can see that the primitive types with private setters are not set.
Questions
Why does the property access modifier only affect properties with primitive types?
Why does this happens when the parameter attribute is set to FromForm but not when it's set to FromBody
Why does the property access modifier only affect properties with primitive types?
For Asp.Net Core ModelBinder, it will check whether the property is private access setter by ComplexTypeModelBinder code below:
protected virtual object CreateModel(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
// If model creator throws an exception, we want to propagate it back up the call stack, since the
// application developer should know that this was an invalid type to try to bind to.
if (_modelCreator == null)
{
// The following check causes the ComplexTypeModelBinder to NOT participate in binding structs as
// reflection does not provide information about the implicit parameterless constructor for a struct.
// This binder would eventually fail to construct an instance of the struct as the Linq's NewExpression
// compile fails to construct it.
var modelTypeInfo = bindingContext.ModelType.GetTypeInfo();
if (modelTypeInfo.IsAbstract || modelTypeInfo.GetConstructor(Type.EmptyTypes) == null)
{
var metadata = bindingContext.ModelMetadata;
switch (metadata.MetadataKind)
{
case ModelMetadataKind.Parameter:
throw new InvalidOperationException(
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForParameter(
modelTypeInfo.FullName,
metadata.ParameterName));
case ModelMetadataKind.Property:
throw new InvalidOperationException(
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForProperty(
modelTypeInfo.FullName,
metadata.PropertyName,
bindingContext.ModelMetadata.ContainerType.FullName));
case ModelMetadataKind.Type:
throw new InvalidOperationException(
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForType(
modelTypeInfo.FullName));
}
}
_modelCreator = Expression
.Lambda<Func<object>>(Expression.New(bindingContext.ModelType))
.Compile();
}
return _modelCreator();
}
Why does this happens when the parameter attribute is set to FromForm but not when it's set to FromBody
For FromBody, it is used JsonInputFormatter to bind the model from the body request, it's used JsonConvert.DeserializeObject to deserilize the object and Newtonsoft.Json support deserize the object which contains private setter from json string.
In my MVC web application, I have linq query that feth the record from database, I want to display that record on view using viewmodel. I have tried with following code.
[HttpGet]
public ActionResult CreatePDF()
{
RentalAgreementEntities db = new RentalAgreementEntities();
String strSession1 = "39726-10275-6027589725",strStatus = "Y",strUserType = "L";
var q = (from um in db.User_Master
join ut in db.UserType_Master on um.US_SerialNo.ToString() equals ut.UT_UserNo
join pu in db.PropertyUser_Master on ut.UT_SerialNo.ToString() equals pu.PU_UserNo
join pr in db.Property_Master on pu.PU_PropertyNo equals pr.PR_SerialNo.ToString()
where pr.PR_MakerID == strSession1
&& ut.UT_Status == strStatus
&& ut.UT_UserType == strUserType
select new
{
um.US_FirstName,
um.US_LastName
}
).AsEnumerable().Select(um => new User_Master {
US_FirstName = um.US_FirstName.ToString(),
US_LastName=um.US_LastName
}).ToList();
var myviewmodel=new viewmodelPDF()
{
lsusermaster=q.ToList();
}
return View("pdfgenerationvw",myviewmodel);
}
I also created viemodel to manage all model's for to display on a view (Here, Just one model access code).
public class viewmodelPDF
{
public List<User_Master> lsusermaster { get; set; }
}
My model class, for which I am going to fetch record from database.
public partial class User_Master
{
public string US_FirstName { get; set; }
public string US_LastName { get; set; }
public int US_SerialNo { get; set; }
}
//Other Models
Now my problem is that, In my action code , when I am trying to assign query result to the lsusermaster of viewmodel then it gives compiler error as belows.
I don't know, why this compile error is thrown, How can I assign query result to viemodel property?
Try this:
var myviewmodel=new viewmodelPDF()
{
lsusermaster=q.ToList()
};
When you are using an object initializer in C#, you can't use ; between the properties, you use it at the end of the initializer
So just remove the ; (or use a ,, as suggested), and move it to the end of the initializer block
var myviewmodel=new viewmodelPDF()
{
lsusermaster=q.ToList()
};
Using a , works even if there are no more properties... it "looks" bad, but it makes easier to add new properties should you ever need them... if the code is final, I'd not use it, but that's personal preference
SOLVED:
The URI was incorrect. Was "h||p://#.#.#.#/:9200" and should have been "h||p://#.#.#.#:9200". This was causing the API to change the port number to 80. Surprised the API actually was able to connect to ElasticSearch instance with the incorrect port number.
I'm new to NEST and ElasticSearch and trying to put together a a simple MatchAll query in NEST.
When I perform a MatchAll from Sense
POST /movies/movie/_search
{
"query": {
"match_all": {}
}
}
I get all the movie objects in the movies index.
But when I try the NEST query
var result = client.Search(s => s.Type("movie").MatchAll());
I get nothing back. Tried setting the return type to a movie class, and still no results.
public class Movie
{
public string title { get; set; }
public string director { get; set; }
public int year { get; set; }
public List<string> genres { get; set; }
}
Also tried .AllIndices() and/or .AllTypes() per this reply
Searching an elasticsearch index with NEST yields no results
Any ideas?
EDIT:
Heres the connection string setting the default index.
ConnectionSettings connection = new ConnectionSettings(uri).UsePrettyResponses().SetDefaultIndex("movies");
You have to check for IsValid on result to see if the call succeeded or not.
result.ConnectionStatus will hold all the information you need to determine what went wrong.
If you'd like to throw exceptions instead you can enable that by calling:
.SetConnectionStatusHandler(c=> {
if (!c.Success)
throw new Exception(c.ToString());
})
on your ConnectionSettings
Calling ToString on an ConnectionStatus object prints all the request and response details in human readable form.