How to bind a collection with objects? - asp.net-core

I need to bind a collection of objects from a querystring, but I cannot find the proper querystring format.
My controller code:
public class Filter
{
public string Name { get; set; }
public string Operator { get; set; }
public object Value { get; set; }
}
public void Get(IEnumerable<Filter> filters)
{
....
}

If you do want to pass the objects with querystring you could try as below:
https://localhost:44389/Test/Index?filters[0].Name=n1&filters[1].Name=n2&filters[2].Name=n3&filters[2].Value=v3
The result:
the offcial document related:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0#collections
but i don't think it's a good solution,because the length of Url is limited,if your model has plenty properties and your collection has many elements ,you may get some error

Related

Asp.Net Core - multiple action methods with the same name and different parameters

I'm looking for a way to have more than one action method with the same name in controller without changing Url (route).
[HTTPPost]
Public ActionResult Method1 (Dto1 param)
{
}
[HTTPPost]
Public ActionResult Method2 (Dto2 param)
{
}
[HTTPPost]
Public ActionResult Method3 (Dto3 param)
{
}
This throws error -
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints
Dto1, Dto2 and Dto3 derive from a base Dto, each have properties specific to different request methods. I am trying to avoid having a single method with a common Dto which will require multiple validations such as validating mandatory fields based on the value of other fields, etc. If we can have 3 different Post methods with different Dtos, it would makes things much easier
Adding Dtos (Simplified)
public class BaseDto
{
public string CommonProp1 { get; set; }
public string CommonProp2 { get; set; }
}
public class Dto1: BaseDto
{
public enumType Type = enumType.Type1;
public string Property1 { get; set; }
}
public class Dto2 : BaseDto
{
public enumType Type = enumType.Type2;
public string Property2 { get; set; }
}
public class Dto3 : BaseDto
{
public enumType Type = enumType.Type3;
public string Property3 { get; set; }
}
You can use Routes or calling a private method from the three above methods, you shouldn't do this as you want. I think your problem is more deep.
But.... if you still want it, here is a workaround.
Instead of receiving an object, receive a string with json content and parse the object.
But you will have to have a property inside the "json object" or another parameter that defines you wich object it is (Dto1, Dto2 or Dto3). In any case will be the same that use different routes or methods because objects are different.
[HTTPPost]
Public ActionResult Method (string param)
{
//Decode your string called param with JSON with a property inside
}
or
[HTTPPost]
Public ActionResult Method (string param, int type)
{
//Decode your string called param with JSON switching "type" as 1, 2 or 3
}
UPDATE after your update:
I suggest you receive BaseDto and the type in other parameter.
[HTTPPost]
Public ActionResult Method (BaseDto param, int type)
{
}

RestSharp ExecAsync<T> where T is a nested object

I have a method that calls RestSharp client ExecuteAsync<T> where the T I am passing is a nested object. I get an exception that says
InvalidProgramException: Request for the resource
/v1/accounts/AN_1476935163870 failed No parameterless constructor
defined for this object.
The object I am passing is:
public class GetAccountResponse : IGetAccountResponse
{
public GetAccountResponseBasicInfo basicInfo { get; set; }
public GetAccountResponseBillingAndPayment billingAndPayment { get; set; }
public GetAccountResponseMetrics metrics { get; set; }
public string billToContact { get; set; }
public string soldToContact { get; set; }
public bool success { get; set; }
}
Where the objects for the properties basicInfo, billingAndPayment, and metrics are classes that have definitions and I have not defined a consturctor. I also have tried replacing the objects with interfaces but it still throws an exception.
Thank you
There are two possible causes of this issue:
Regression in RestSharp that added the new constraint to typed request methods, which was fixed this week
Serialization issue. In that case, you can try another serializer, we have libraries that support NewtosoftJson, Utf8Json and System.Text.Json in addition to the built-in SimpleJson

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.

this[propertyName] is not a function in breeze.debug.js

I am using Hot towel template and extended functionality of it by using breeze. I have used breeze.partial-entities.js file to conver breeze entities to proper dtos that can be used by knockout observables as shown below.
function dtoToEntityMapper(dto) {
var keyValue = dto[keyName];
var entity = manager.getEntityByKey(entityName, keyValue);
if (!entity) {
// We don't have it, so create it as a partial
extendWith = $.extend({ }, extendWith || defaultExtension);
extendWith[keyName] = keyValue;
entity = manager.createEntity(entityName, extendWith);
}
mapToEntity(entity, dto);
entity.entityAspect.setUnchanged();
return entity;
}
For few of the entities it is working properly and getting breeze data converted to entities but for one of the entity implementation is failing. Model for the same is given as below.
public class StandardResourceProperty
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int StandardResourceId{ get; set; }
public int InputTypeId{ get; set; }
public int ListGroupId{ get; set; }
public string Format{ get; set; }
public string Calculation{ get; set; }
public bool Required{ get; set; }
public int MinSize{ get; set; }
public int MaxSize{ get; set; }
public string DefaultValue{ get; set; }
public string Comment { get; set; }
public virtual StandardResource AssociatedStandardResource { get; set; }
public virtual List AssociatedList { get; set; }
}
The error i am getting is
TypeError: this[propertyName] is not a function
[Break On This Error]
thispropertyName;
breeze.debug.js (line 13157)
]
with code
proto.setProperty = function(propertyName, value) {
this[propertyName](value);
// allow set property chaining.
return this;
};
Please let me know . What can be possible issue with the implementation also , it would be great if i can get more suggestion on how to debug and trace such issues.
Let's back up. I do not understand what you mean by "convert breeze entities to proper dtos that can be used by knockout observables". Breeze entities are already configured as KO observables (assuming you are using the default Breeze model library configuration). What are you trying to do?
I suspect you are following along with the Code Camper Jumpstart course where it does a getSessionPartials projection query. That query (like all projections) returns DTOs - not entities - and maps them with the dtoToEntityMapper method into Session entities.
The CCJS dtoToEntityMapper method cannot be used with entities. It is for converting from a DTO to an Entity and takes DTOs - not entities - as input.
Goodbye to dtoEntityMapper
The dtoToEntityMapper method pre-dates the ability of Breeze to automate projection-to-entity mapping by adding .toType('StandardResourceProperty') to your projection query.
Here is what the CCJS getSessionPartials query could look like now:
var query = EntityQuery
.from('Sessions')
.select('id, title, code, speakerId, trackId, timeSlotId, roomId, level, tags')
.orderBy(orderBy.session)
.toType('Session');
If you go this way, be sure to set the default state of the isPartial flag to true in the custom constructor (see model.js)
metadataStore.registerEntityTypeCtor(
'Session', function () { this.isPartial = true; }, sessionInitializer);
Note that this.isPartial = true is the reverse of the CCJS example where the default was false.
Make sure that you set isPartial(false) when you query or create a full entity. In CCJS there are two places to do that: in the success-callback of getSessionById AND in createSession which would become:
var createSession = function () {
return manager.createEntity(entityNames.session, {isPartial: false});
};

To those having the Object graph for type 'xxx' contains cycles

This is more of a share knowledge than anything else. Wasn't sure if I was supposed to make it a question.
So I have multiple issues with the following error Object graph for type 'xxx' contains cycles
There are many ways the internet says to fix it, for instance adding the IsReference=true attribute, creating your own custom attributes, etc.
For me I find the most simplest way is by just making the child parent object have a private setter.
ie.
public ParentObject objectName { get; private set; }
More in depth example.
public class Parent()
{
public Guid ID { get; set; }
public string Name { get; set; }
}
public class Child()
{
public Guid ID { get; set; }
public Parent Parent { get; private set; }
public int ChildProp { get; set; }
}
I'll give the credit to this post correct answer(in green), for helping me figure it out.
http://forums.asp.net