ASP.NET Core Webapi (3.x) Custom ModelBinder Always Returns Empty Object - asp.net-core

I've been attempting to create a custom model binder and am running into an issue where the object after going through the binder is ALWAYS empty.
I have a good reason for using a custom model binder. For the purposes of this question, assume I HAVE to use a custom model binder. In every case where i've used a custom binder provider with either stock or custom modelbinders I've run into this issue, so I've dumbed this down a LOT to demonstrate, but it happens in this specific example as well, and I really need to know why.
I have a simple controller action and DTO class:
[HttpPost]
[Route("{id}")]
public async Task<ActionResult<QueryServicesDto>> UpdateQueryService(int id,
[FromBody] QueryServicesDtoLight dto)
{
}
public class QueryServicesDtoLight
{
public long QueryServicesId { get; set; }
public DateTime? CreationDate { get; set; }
public long? ModifiedDate { get; set; }
public long? PropagationDate { get; set; }
public int? CabinetListNumber { get; set; }
public string GameCode { get; set; }
public string Status { get; set; }
}
Can anyone tell me why when posting valid JSON to this action with no custom binder providert I get a DTO with the proper values, but if I inject the custom modelBinderProvider below I get a newed up model with no values?
public class QueryServiceModelBinderBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType== typeof(QueryServicesDtoLight))
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++)
{
ModelMetadata theProp = context.Metadata.Properties[i];
var binder = context.CreateBinder(theProp);
propertyBinders.Add(theProp, binder);
}
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
return new ComplexTypeModelBinder(propertyBinders, loggerFactory);
}
else
{
return null;
}
}
}
Binder provider is added like this::
services.AddControllersWithViews(o=>o.ModelBinderProviders.Insert(0, new QueryServiceModelBinderBinderProvider())).AddNewtonsoftJson();
Sample JSON Data
{"queryServicesId":14,"creationDate":"2021-03-08T17:06:36.053","modifiedDate":16176433093000000,"propagationDate":0,"cabinetListNumber":996,"gameCode":"PGA2006","status":"AC"}

Related

how to send array to API which contains image and other data in .net core

When I am passing a single object like below then it is working as per below image
[HttpPost]
public async Task<ActionResult> Post([FromForm] MyModel Details)
{
}
but when I am passing the List of the object to API then it is not working. option to upload a file is not visible. and if I entered any values in the array then also I am getting count 0 for details.
[HttpPost]
public async Task<ActionResult> Post([FromForm] List<MyModel> Details)
{}
I want to pass the List of images and descriptions to API. How can I achieve it?
Thanks in advance!
You need custom model binding for the list model . Here is a similar demo:
custom model binding code:
public class MetadataValueModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var values = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (values.Length == 0)
return Task.CompletedTask;
var options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = true };
var deserialized = JsonSerializer.Deserialize(values.FirstValue, bindingContext.ModelType, options);
bindingContext.Result = ModelBindingResult.Success(deserialized);
return Task.CompletedTask;
}
}
Add the model binder to the model class:
public class MasterDTO
{
public string Comments { get; set; }
public IFormFile File { get; set; }
public List<DetailDTO> Details { get; set; }
public MasterDTO()
{
this.Details = new List<DetailDTO>();
}
}
[ModelBinder(BinderType = typeof(MetadataValueModelBinder))]
public class DetailDTO
{
public Int64 ElementId { get; set; }
public double LowerLimit { get; set; }
public double HigherLimit { get; set; }
public string Status { get; set; }
public string UserAuthorization { get; set; }
public DateTime? AutorizationDate { get; set; }
}
controller/action
[HttpPost]
public async Task<IActionResult> CreateProjectLimit([FromForm] MasterDTO masterDto)
{
//...
return Ok();
}
You can just use postman to pass the list of images and Descriptions to API
Below is the right answer. we can use Postman to pass images in the array as shown below.

Asp.Net Core - How to create object-specific sub-routes

I have a model class Dispute with one-to-many relationships.
I would navigate and perform CRUD operation on its related objects within a specific disputeId.
I would compose the url as follow:
Disputes/Details/(disputeId)/(related_objects)
where related_objects can be, for example, Persons, God, etc.
What kind of approach i can use?
You could use attribute routing to realize the route. You need to pass navigation properties as your relative_objects.Refer to my demo:
1.Model:
public class Dispute
{
[Key]
public int DisputeId { get; set; }
public List<Person> Persons{ get; set; }
}
2.DbContext:
public DbSet<Dispute> Disputes{ get; set; }
public DbSet<Person> Persons{ get; set; }
3.Controller:
[Route("Disputes")]
public class DisputesController : Controller
{
private readonly ApplicationDbContext _context;
public ProductsController(ApplicationDbContext context)
{
_context = context;
}
// GET: Disputes/Details/5/Persons
[Route("Disputes/{disputeId}/{related_objects}")]
public async Task<IActionResult> Details(int? disputeId, string related_objects)
{
if (disputeId== null)
{
return NotFound();
}
var dispute = await _context.Disputes.Include(related_objects)
.FirstOrDefaultAsync(m => m.DisputeId == disputeId);
//other logic
}
}

asp.net core custom model binder just for one property

I have a simple model for my asp.net core controller:
[HttpPost]
public async Task<DefaultResponse> AddCourse([FromBody]CourseDto dto)
{
var response = await _courseService.AddCourse(dto);
return response;
}
My model is :
public class CourseDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Genre { get; set; }
public string Duration { get; set; }
public string Level { get; set; }
public string AgeRange { get; set; }
public string Notes { get; set; }
public bool Active { get; set; }
public string OrganisationCode { get; set; }
}
I'm trying to set value of "OrganisationCode" using a custom mode binder or action filter, but had no success.
I would be thnakful if you advise whats the right way to updat ethe model before executing the action.
Thanks.
I will show you here a very simple custom model binder I have just written (and tested in .Net Core 2.0):
My model binder:
public class CustomModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var value = valueProviderResult.FirstValue; // get the value as string
var model = value.Split(",");
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
My model (and notice, only one property has my custom model binder annotation):
public class CreatePostViewModel
{
[Display(Name = nameof(ContentText))]
[MinLength(10, ErrorMessage = ValidationErrors.MinLength)]
public string ContentText { get; set; }
[BindProperty(BinderType = typeof(CustomModelBinder))]
public IEnumerable<string> Categories { get; set; } // <<<<<< THIS IS WHAT YOU ARE INTERESTER IN
#region View Data
public string PageTitle { get; set; }
public string TitlePlaceHolder { get; set; }
#endregion
}
What it does is: it receives some text like "aaa,bbb,ccc", and converts it into array, and return it to the ViewModel.
I hope that helps.
DISCLAIMER: I am not an expert in model binders writing, I have learnt that 15 minutes ago, and I found your question (with no helpful answer), so I tried to help. This is a very basic model binder, some improvements are surely required. I learned how to write it from the official documentation page.
The [FromBody] attribute you are using on the action parameter. means that you direct the default behavior of Model Binding to use the formatters instead. That is why your custom Model Binder does not work.
And [FromBody] is reading the content (request body). So you won't get the request body from your Action Filter, as the request body is a non-rewindable stream, so it suppose to be read only once (I'm assuming that you are trying to read the request body from Action Filter).
My suggestion is to use your custom model binder and remove the FromBody Attribute.

WCF with Entity Framework Code First relationship

I'm learning WCF, and tried to make a small service that exposes a Project and its tasks (the standard Entity Framework hello world).
The class structure is the following:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime CreationDate { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int TaskId { get; set; }
public string Title { get; set; }
public virtual Project RelatedProject { get; set; }
}
The DB context comes after:
public class ProjectContext : DbContext
{
public DbSet<Project> Projects { get; set; }
public DbSet<Task> Tasks { get; set; }
}
Finally, the service endpoint:
public IEnumerable<Project> getProjects()
{
ProjectContext p = new ProjectContext();
return p.Projects.AsEnumerable();
}
The problem is that this model will throw a System.ServiceModel.CommunicationException, but, If I remove the virtual properties from the model, It would work, but I would loose the entity framework links between Project and Task.
Anyone with a similar setup?
I banged my head against the wall several hours with this one. After extensive debugging, google gave the answer and I feel right to post it here since this was the first result I got in google.
Add this class on top of your [ServiceContract] interface declaration (typically IProjectService.cs
public class ApplyDataContractResolverAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
{
}
public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
{
var dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver =
new ProxyDataContractResolver();
}
public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
var dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver =
new ProxyDataContractResolver();
}
public void Validate(OperationDescription description)
{
// Do validation.
}
}
Requirements are
using System.ServiceModel.Description;
using System.Data.Objects;
using System.ServiceModel.Channels;
Then under the [OperationContract] keyword add [ApplyDataContractResolver] keyword and you are set!
Big thanks to http://blog.rsuter.com/?p=286
For sending data trough WCF you should disable lazy loading (dataContext.ContextOptions.LazyLoadingEnabled = false;).
To be sure the data you want is loaded you need to use eager loading ( trough the Include method).
You need to change your function to:
public IEnumerable<Project> getProjects()
{
ProjectContext p = new ProjectContext();
p.ContextOptions.LazyLoadingEnabled = false;
return p.Projects.Include("Tasks").AsEnumerable();
}

WCF Service Library

I am new to WCF services. I was asked to manually create a WCF service. I did the following:
Created a new project Console App.
Created a class called Evaluation
Created an interface called IEvaluatorService
Created a class EvaluationService implementing the interface IEvaluatorService
I need to use the following address: http://localhost:8000/Evaluations then test my service via WcfTestClient. I am not sure what to do next. Code below.
Thanks in advance for any help!
namespace Evaluations
{
[ServiceContract]
interface IEvaluatorService
{
[OperationContract(Name="AddEvaluation")]
int Add(string user, string content);
[OperationContract(Name="RemoveEvaluation")]
void Remove([MessageParameter(Name="existingID")] int id);
[OperationContract(Name="GetAllEvaluations")]
Evaluation[] GetAll();
[OperationContract(Name="GetEvaluation")]
Evaluation Get(int id);
[OperationContract(Name="GetAllEvaluationsFrom")]
Evaluation[] GetAll([MessageParameter(Name = "username")] string submitter);
}
}
namespace Evaluations
{
class EvaluationService : IEvaluatorService
{
List<Evaluation> myList = new List<Evaluation>();
static int count = 0;
public int Add(string user, string content)
{
Evaluation eval = new Evaluation()
{
UniqueID = count++,
Submitter = user,
SubmissionTime = DateTime.Now,
Text = content
};
myList.Add(eval);
return eval.UniqueID;
}
public void Remove(int id)
{
myList.RemoveAt(id);
}
public Evaluation[] GetAll()
{
return myList.ToArray<Evaluation>();
}
public Evaluation Get(int id)
{
throw new NotImplementedException();
}
public Evaluation[] GetAll(string submitter)
{
throw new NotImplementedException();
}
}
}
namespace Evaluations
{
[DataContract]
class Evaluation
{
[DataMember]
public string Submitter { get; set; }
[DataMember]
public int UniqueID { get; set; }
[DataMember]
public DateTime SubmissionTime { get; set; }
[DataMember]
public string Text { get; set; }
}
}
The easiest thing to do is...
go into Visual Studio
right click on your project
select Add New
choose WCF Service
See what code Visual Studio added and follow that pattern for your service.