StackOverflowException when loading Microsoft.OData.Client.DataServiceCollection (v6.6.0) with items while having TrackingMode.AutoChangeTracking - client

I am trying to get entities from an ODATA service like this:
DataServiceCollection<Asset> entitiesServiceCollection =
new DataServiceCollection<Asset>(this._dataContext.Assets, TrackingMode.AutoChangeTracking );
The collection constructor throws the StackOverflowException unless I make TrackingMode.None.
This code used to work before switched the EF model-first data definition of the service side to code-first approach. Now, with the code-first approach the service looks fine but the client side is broken when it tries to use change tracking. Did I miss something during the model-first-to-code-first transition?
Here is how the Asset is defined:
public class Asset
: EntityBase, IEntity<Guid>, IIdentifiable, IObjectStateHost, IAsset
{
private Guid _id = Guid.Empty;
private Identity _identity = null;
private byte[] _content = null;
private AssetContentKind _contentKind = AssetContentKind.Undefined;
public Asset()
: this(AssetContentKind.Undefined)
{
}
public Asset(AssetContentKind contentKind)
{
_id = Guid.NewGuid();
_identity = new Identity();
_presentationalIdentities = new HashSet<PresentationalIdentity>();
_contentKind = contentKind;
}
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id
{
get { return _id; }
set
{
_id = value;
}
}
public Identity Identity
{
get { return _identity; }
set
{
_identity = value;
}
}
public byte[] Content
{
get { return _content; }
set { _content = value; }
}
public AssetContentKind ContentKind
{
get { return _contentKind; }
set { _contentKind = value; }
}
IIdentity IIdentifiable.Identity
{
get { return this.Identity; }
}
public override string ToString()
{
return Utils.StringRenderingUtil.Render(this);
}
}
Here is the call stack during the exception:
[Managed to Native Transition]
mscorlib.dll!System.RuntimeType.GetGenericArguments() Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsDataServiceCollection(System.Type collectionType, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsEntityType(System.Type type, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsDataServiceCollection(System.Type collectionType, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsEntityType(System.Type type, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsDataServiceCollection(System.Type collectionType, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsEntityType(System.Type type, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsDataServiceCollection(System.Type collectionType, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsEntityType(System.Type type, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsDataServiceCollection(System.Type collectionType, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsEntityType(System.Type type, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsDataServiceCollection(System.Type collectionType, Microsoft.OData.Client.ClientEdmModel model) Unknown
...
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsDataServiceCollection(System.Type collectionType, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsEntityType(System.Type type, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsDataServiceCollection(System.Type collectionType, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsEntityType(System.Type type, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsDataServiceCollection(System.Type collectionType, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsEntityType(System.Type type, Microsoft.OData.Client.ClientEdmModel model) Unknown
Microsoft.OData.Client.dll!Microsoft.OData.Client.BindingEntityInfo.IsDataServiceCollection(System.Type collectionType, Microsoft.OData.Client.ClientEdmModel model) Unknown
The maximum number of stack frames supported by Visual Studio has been exceeded.
I looked into the public source code of BindingEntityInfo - how the two recursive methods in the call stack are implemented. And it seems that the recursion may happen under a couple of conditions:
either the data service collection is not getting to realize that its concrete T type Asset is an entity of the model,
or the data service collection for some reason has no definition of the EDMX model in use.
That leads me to believe that I might need to feel in extra code-first settings/configuration when I move from perfectly working model-first to now broken code-first to fix it. The question is what are these "extra-settings"?

I try to repro the problem. But it seems ok on my side. Here's my detail repro steps, hope it can help you:
Use your Asset class to model, but remove the unknown types as follows :
public class Asset
{
private Guid _id = Guid.Empty;
private byte[] _content = null;
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id
{
get { return _id; }
set
{
_id = value;
}
}
public byte[] Content
{
get { return _content; }
set { _content = value; }
}
}
Create the Context DB:
public class AssetContext : DbContext
{
public DbSet<Asset> Assets { get; set; }
}
Add the controller:
public class AssetsController : ODataController
{
private AssetContext db = new AssetContext();
public AssetsController()
{
if (!db.Assets.Any(e => e.Id == new Guid("524BC73C-1545-4B5C-AD57-09B9FD8DB860")))
{
db.Assets.Add(new Asset
{
Id = new Guid("524BC73C-1545-4B5C-AD57-09B9FD8DB860"),
Content = new byte[] {0, 2, 32, 64, 128, 255}
});
db.SaveChanges();
}
}
public IHttpActionResult Get()
{
return Ok(db.Assets);
}
}
Test by running the service and querying the Assets. The result payload is:
{
"#odata.context":"http://localhost:52286/odata/$metadata#Assets","value":[
{
"Id":"524bc73c-1545-4b5c-ad57-09b9fd8db860","Content":"AAIgQID/"
}
]
}
Based on the OData Client blog, build a console application.
Test the OData Client as:
Container container = new Container(new Uri("http://localhost:52286/odata/"));
Console.WriteLine("Container created");
DataServiceCollection<Asset> entitiesServiceCollection =
new DataServiceCollection<Asset>(container.Assets, TrackingMode.AutoChangeTracking);
Console.WriteLine("DataServiceCollection created");
No exception throw.

Related

Enum not coverting expando object to XML in .net Core Web API GET response

I have a get method on a .net Core 3.1 Web API controller that returns an expando object which is generated from a model class.
The model contains an enum value:
public class MyModel
{
public int Id { get; set; }
public string Name { get; set; }
public Gender Gender { get; set; }
}
public enum Gender
{
Male,
Female
}
[HttpGet("{id}")]
public async Task<IActionResult> GetAsync(int id)
{
var recordFromDB = await dbService.GetAsync(id);
if (recordFromDB == null)
return NotFound();
var returnModel = mapper.Map<MyModel>(recordFromDB).ShapeData(null);
return Ok(returnModel);
}
public static ExpandoObject ShapeData<TSource>(this TSource source, string fields)
{
var dataShapedObject = new ExpandoObject();
if (string.IsNullOrWhiteSpace(fields))
{
var propertyInfos = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var propertyInfo in propertyInfos)
{
var propertyValue = propertyInfo.GetValue(source);
((IDictionary<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);
}
return dataShapedObject;
}
... more of the method here but it's never hit so I've removed the code
}
If I do a get request for this record with the accept header application/json it all works fine.
if I change the accept header to application/xml I receive the following error:
System.Runtime.Serialization.SerializationException: Type 'Gender'
with data contract name 'Gender' is not expected. Add any types not
known statically to the list of known types - for example, by using
the KnownTypeAttribute attribute or by adding them to the list of
known types passed to DataContractSerializer.
If I remove the enum value from the model the XML is generated fine.
Where do I add the KnownTypeAttribute or how do I get around this error?
Thanks in advance

Mediator Api call failing

I'm trying to make a simple request using mediator and .net core. I'm getting an error that I am not understanding. All I'm trying to do is a simple call to get back a guid.
BaseController:
[Route("api/[controller]/[action]")]
[ApiController]
public class BaseController : Controller
{
private IMediator _mediator;
protected IMediator Mediator => _mediator ?? (_mediator = HttpContext.RequestServices.GetService<IMediator>());
}
Controller:
// GET: api/Customer/username/password
[HttpGet("{username}/{password}", Name = "Get")]
public async Task<ActionResult<CustomerViewModel>> Login(string username, string password)
{
return Ok(await Mediator.Send(new LoginCustomerQuery { Username = username,Password = password }));
}
Query:
public class LoginCustomerQuery : IRequest<CustomerViewModel>
{
public string Username { get; set; }
public string Password { get; set; }
}
View Model:
public class CustomerViewModel
{
public Guid ExternalId { get; set; }
}
Handler:
public async Task<CustomerViewModel> Handle(LoginCustomerQuery request, CancellationToken cancellationToken)
{
var entity = await _context.Customers
.Where(e =>
e.Username == request.Username
&& e.Password == Encypt.EncryptString(request.Password))
.FirstOrDefaultAsync(cancellationToken);
if (entity.Equals(null))
{
throw new NotFoundException(nameof(entity), request.Username);
}
return new CustomerViewModel
{
ExternalId = entity.ExternalId
};
}
This is the exception I am getting:
Please let me know what else you need to determine what could be the issue. Also, be kind I have been away from c# for a while.
Thanks for the info it was the missing DI. I added this
// Add MediatR
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>));
services.AddMediatR(typeof(LoginCustomerQueryHandler).GetTypeInfo().Assembly);
and we are good to go.

.net core custom model binding

I have a model such as
public class MyModel
{
public MyObject myObject {get;set;}
}
public class MyObject
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
With out using a custom model binder everything works great. I am trying to implement a model binder and not getting anywhere -- the resources that I have come from are
https://www.youtube.com/watch?v=qDRORgoZxZU (returns null model to the controller)
http://intellitect.com/custom-model-binding-in-asp-net-core-1-0/ (controller dies on the constructor)
http://hotzblog.com/asp-net-vnext-defaultmodelbinder-and-automatic-viewmodel-string-trim/ (can not even find MutableObjectModelBinder in the .net-core namespace)
Ideally what I want is to track which properties where set by the ModelBinder.
public class MyObject
{
public string FirstName {get;set;}
public string LastName {get;set;}
public List<String> ModifiedProperties {get;set;}
}
when the object is created by the ModelBinder for each property that is being set it adds it to the ModifiedProperties list.
This is solution. You need to implement IModelBinderProvider and IModelBinder
public class EntityFrameworkModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
//We only want to invoke the CustomeBinder on IBaseEntity classes
if (context.Metadata.ContainerType != null && context.Metadata.ContainerType.GetInterfaces().Contains(typeof(SurgeOne.Core.IBaseEntity)))
{
//We only create the custom binder on value types. E.g. string, guid, etc
if (context.Metadata.ModelType.GetTypeInfo().IsValueType ||
context.Metadata.ModelType == typeof(System.String))
{
return new EntityFrameworkModelBinder();
}
}
return null;
}
}
And IModelBinder
public class EntityFrameworkModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
//Get the value
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None)
{
// no entry
return Task.CompletedTask;
}
//Set the value -- not sure what this does
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
//Set the value -- this has to match the property type.
System.ComponentModel.TypeConverter typeConverter = System.ComponentModel.TypeDescriptor.GetConverter(bindingContext.ModelType);
object propValue = typeConverter.ConvertFromString(valueProviderResult.FirstValue);
bindingContext.Result = ModelBindingResult.Success(propValue);
//Code to track changes.
return Task.CompletedTask;
} //BindModelAsync
}

input date error with thymeleaf

I have a html code:
<input type="date" th:field="*{birthday}"/>
When I submit the form I get an error:
Failed to convert property value of type [java.lang.String] to required type [java.util.Calendar] for property birthday; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [java.util.Calendar] for property birthday: no matching editors or conversion strategy found
I'm using Spring. How can I fix?
Thanks.
The controller:
#RequestMapping(value = "/edit/{id}", method = RequestMethod.POST)
public String updateMember(Model model,
#Valid Member member,
BindingResult bindingResult,
#RequestParam(value="action", required=true) String action) {
System.out.println("updateMember POST - start");
System.out.println(member);
if (bindingResult.hasErrors()) {
System.out.println("bindingResult.hasErrors");
return "member_edit";
}
memberService.update(member);
// todo verificar se precisa dessa linha mesmo chamando o redirect
model.addAttribute("members", memberService.getAll());
return "redirect:/";
}
Spring don't understand the "birthdate" field in java.lang.String because in your Bean it's a java.util.Calendar.
You need to configure the converter. Something like that :
#Configuration
public class ConversionServiceConfig extends WebMvcConfigurerAdapter {
#Bean
public CalendarFormatter calendarFormatter() {
return new CalendarFormatter();
}
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(calendarFormatter());
}
}
And your CalendarFormatter looks like this :
public class CalendarFormatter implements Formatter<Calendar> {
final String defaultDateFormat = "dd.MM.yyyy";
#Override
public String print(Calendar object, Locale locale) {
return new SimpleDateFormat(defaultDateFormat).format(object.getTime());
}
#Override
public Calendar parse(String text, Locale locale) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(defaultDateFormat);
Date date = sdf.parse(text);
return sdf.getCalendar();
}
}

Web API controller returning boolean

I have a Web API where one of the methods in a controller return true or false when validating user id which is a string of numbers. I do no have an actual database yet, so I sort of mocked up the set of values in the repository.
Below is my code:
My repository class:
public class myRepository
{
public myClasses.Employee[] GetAllEmployees()
{
return new myClasses.Employee[]
{
new myClasses.Employee
{
empId="111111",
empFName = "Jane",
empLName="Doe"
},
new myClasses.Employee
{
empId="222222",
empFName = "John",
empLName="Doe"
}
};
}
public bool VerifyEmployeeId(string id)
{
myClasses.Employee[] emp = new myClasses.Employee[]
{
new myClasses.Employee
{
empId="111111",
empFName = "Jane",
empLName="Doe"
},
new myClasses.Employee
{
empId="222222",
empFName = "John",
empLName="Doe"
}
};
for (var i = 0; i <= emp.Length - 1; i++)
{
if (emp[i].empId == id)
return true;
}
return false;
}
}
and my model class:
public class myClasses
{
public class Employee
{
public string empId { get; set; }
public string empFName { get; set; }
public string empLName { get; set; }
}
}
and here is my controller:
public class myClassesController : ApiController
{
private myRepository empRepository;
public myClassesController()
{
this.empRepository = new myRepository();
}
public myClasses.Employee[] GetEmployees()
{
return empRepository.GetAllEmployees();
}
public bool VerifyEmployee(string id)
{
return empRepository.VerifyEmployeeId(string id);
}
}
Now when i compile it I get an error:
} expected
Type or namespace definition, or end-of-file expected
; expected
in line
return empRepository.VerifyEmployeeId(string id);
of my controller.
My question is using boolean the best way to return Success or Failure from Web API method or is there a better way? and also why am I getting this error. I am new to Web API
The compile error is caused by this;
return empRepository.VerifyEmployeeId(string id);
You should rewrite to:
return empRepository.VerifyEmployeeId(id);
You don't have you specify the type of the argument when calling a function.
About returning true or false; if you intend to only check whether the employee is valid or not, I should leave it this way. If you plan to use that employee data more you could rewrite that function so it returns the actual employee itself, and return 404: Not Found when the Employee is not found for instance.

Resources