Xamarin Forms and EntityFramework Attributes compatibility - c#

I have a client/server solution using C#, WPF, ASP.NET WebAPI and Entity Framework. Client and server clases share the model among his projects. Now I am trying to create a new client, using Xamarin Forms and sharing the model to, but Entity Framework attributes(MaxLength, Index, NotMapped, etc), are not compatible in a PCL. So this are the things that I've tried:
Import Microsoft.EntityFrameworkCore to the PCL Model
As described here, you should be able to use entity framework with Xamarin forms, so I convert the PCL to NetStandard 1.3, and it works, every EntityFramework attribute is allowed. But now the server project is not compatible with that standard and I cannot add packages like prism and Newtonsoft.Json in the model project.
Mock the attributes for Xamarin forms using the bait and switch trick
I've tried the approach described here, based on creating custom attributes in the model PCL, and redefining them in the class libraries. MyClient.Droid and MyClient.UWP redefine the attributes leaving them empty, and MyServer will redefine them with the Entity Framework functionality.
Custom IndexAttribute - Model PCL:
namespace Model.Compatibility
{
public class IndexAttribute : Attribute
{
public IndexAttribute()
{
}
}
}
Custom IndexAttribute - Server side:
[assembly: TypeForwardedToAttribute(typeof(Model.Compatibility.IndexAttribute))]
namespace Model.Compatibility
{
public class MockedIndexAttribute : System.ComponentModel.DataAnnotations.Schema.IndexAttribute
{
public MockedIndexAttribute()
{
}
}
}
I test this aproach calling var attribute = new Model.Compatibility.IndexAttribute();. MockedIndexAttribute constructor is never called.
Create a Shared Project Instead of PCL
This way is a little more messy, but looks like it works. Just creating a new shared project for the model, and using conditional flags like this:
#if !__MOBILE__
[NotMapped, Index]
#endif
public Guid Id { get; set; }
I've not fully deployed this approach at the moment, but if I cannot make none of the first two ways working, I will go with this.
EDIT - Trying to make the "Bait and Switch Attributes" approach work
As #AdamPedley sugested and this thread to, I've redefined IndexAttribute in a new PCL(Xamarin.Compatibility), using the same namespace as the original one:
namespace System.ComponentModel.DataAnnotations.Schema
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class IndexAttribute : Attribute
{
public IndexAttribute() { }
}
}
Now, my PCL Model includes a reference to Xamarin.Compatibility, so I can use Index attribute in my model properties:
[Index]
public Guid Id { get; set; }
Then, from my Server project, I call the next line of code to check what constructor is called, the custom attribute, or the one defined by EntityFramework:
PropertyInfo prop = typeof(MyClass).GetProperty("Id");
object[] attributes = prop.GetCustomAttributes(true);
The constructor called is the custom one, so it does not work because it have to call to the attribute defined by EntityFramework. Thats is the thing that I don't know, what is the mechanism that make my model's PCL select custom attribute or EF attribute depending on the calling assembly.
I've also added a file in my server project, called TypeForwarding.Net.cs(as sugested here), that contains:
[assembly: TypeForwardedTo(typeof(IndexAttribute))]
But still not working.

I believe the EF fluent API is PCL and NetStandard friendly. Thus you can create POCO objects and let the fluent api do the cross platform mappings instead of using attributes. msdn.microsoft.com/en-us/library/jj591617(v=vs.113).aspx
Note: I did this with a project using EF6 and PCL projects to share across MVC / WPF / Mobile

Related

AutoMapper stops working after moving models to another project

I have an api that uses code first entity framework, and uses AutoMapper to map the entities to models before surfacing the data to the consumer. The mapping profiles exist in the same file as the respective model classes.
I'm trying to restructure the projects a bit and pull the models out into a separate project while keeping the mapping profiles where they are in the existing project. And updating the reference to point to the new project after the models are removed locally, of course. Upon doing so, AutoMapper stops working.
Before the restructure
The structure of the solution before the restructure is as follows:
Api project (Houses the controllers and api endpoints)
Startup.cs
public void ConfigureServices(IServiceCollection services) {
...
services.AddAutoMapper(typeof(LocaleModel).GetTypeInfo().Assembly)
...
}
Application project (Houses the MediatR handlers, models, mapping profiles, etc.)
Models/LocaleModel.cs
public class LocaleModel {
public long Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
public class LocaleModelMapping() : Profile {
public LocaleModelMapping() {
CreateMap<Locale, LocaleModel>();
CreateMap<LocaleModel, Locale>();
}
}
Queries/Locales/Get/GetLocalesRequestHandler.cs
public async Task<IEnumerable<LocaleModel>> Handle(GetLocalesRequest request, CancellationToken cancellationToken) {
var locales = await DbContext.Locales
.AsNoTracking()
.ToListAsync(cancellationToken);
return Mapper.Map<List<LocaleModel>>(locales);
}
After the restructure
The structure of the solution after the restructure:
Api project (Houses the controllers and api endpoints)
Startup.cs
public void ConfigureServices(IServiceCollection services) {
...
services.AddAutoMapper(typeof(LocaleModelMapping).GetTypeInfo().Assembly)
...
}
Application project (Houses the MediatR handlers, models, mapping profiles, etc.)
Mappings/LocaleModelMapping.cs
public class LocaleModelMapping() : Profile {
public LocaleModelMapping() {
CreateMap<Locale, LocaleModel>();
CreateMap<LocaleModel, Locale>();
}
}
Models project (Houses the models only)
LocaleModel.cs
public class LocaleModel {
public long Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
The references were updated as necessary so that the Application project is aware of the Models project.
The moment I remove the models from the Application project and refer to the Models project, AutoMapper stops working, even though I updated Startup.cs to use the assembly of a mapping profile from the Application project.
The error that is generated is as follows (full namespace censored):
Mapping types:
List`1 -> List`1
List[...Domain.Locale, ...Domain, Version=2.0.0.0]
-> List[...Application.Models.LocaleModel, ...Application.Models, Version=1.0.0.0]
---> AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Things I've tried:
Putting all the mapping profiles in a single class.
Several different ways of passing in the assembly, types, etc. into services.AddAutoMapper().
Registering the mapping profiles manually in Startup.cs.
Mirrored another project with the same intended structure where it works just fine without issue.
Any help is appreciated!
Moving the Model and mapping should not have required any change to the service.AddAutomapper call beyond where the project would resolve the Assembly reference. You should have been able to leave the Startup.cs as services.AddAutoMapper(typeof(LocaleModel).GetTypeInfo().Assembly); The difference would have been the namespace where "LocaleModel" would have resolved from.
The first thing I would check is if you accidentally selected an Intellisense option to Create a new LocaleModelMapping class when you updated the Startup.cs so instead of it appending a using block to the new namespace in your Application project, it created a dummy LocaleModelMapping class somewhere in your API project leaving Automapper trying to resolve mappings from the API project still instead of the Application project. You can verify this by right clicking on the LocaleModelMapping inside (typeof(LocaleModelMapping).... and select "Go to Definition". Does this navigate you into your Application project, to an empty class in your API project, or somewhere else? (I.e. disassembly of a stale assembly reference)
Found the solution after a bit more searching.
In our dependency injection Autofac module that is called from ConfigureContainer(...) within Startup.cs, there was some code that was registering the AutoMapper profiles using one of the models. Which used to be coupled together with the mapping prior to me pulling them out into a separate project, but is no longer.
// AutoMapper Profiles
var profiles = typeof(LocaleModelMapping).Assembly.GetTypes()
.Where(t => typeof(Profile).IsAssignableFrom(t))
.Select(t => (Profile)Activator.CreateInstance(t));
builder.Register(ctx => new MapperConfiguration(cfg =>
{
foreach (var profile in profiles) cfg.AddProfile(profile);
}));
builder.Register(ctx => ctx.Resolve<MapperConfiguration>().CreateMapper())
.As<IMapper>()
.InstancePerLifetimeScope();
Simply updating the reference to use a mapping class instead of a model class fixed the issue.
Alternatively, removing the code altogether also worked. The Startup.cs code is already registering the AutoMapper profiles via services.AddAutoMapper(...), so this code in the Autofac module is not necessary.

ASP.NET Core Web API Model binding behavior change

I have simple controller with custom model type Heading - without parameterless constructor and public setter.
I tried following code in asp.net mvc core 2.2 and 3.1.
Model class:
public class Heading
{
public string Title { get; }
public Heading(string title)
{
Title = title;
}
}
API Controller:
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpPost]
public void Post([FromBody] Heading value)
{
}
}
With .net core 2.2, binding works perfectly. But for core 3.1, it throws error
System.NotSupportedException: Deserialization of reference types
without parameterless constructor is not supported. Type
'WebApplication3.Controllers.Heading' at
System.Text.Json.ThrowHelper.ThrowNotSupportedException_DeserializeCreateObjectDelegateIsNull(Type
invalidType)
Is this change in behaviour? Can it still be achieved?
In ASP.NET Core 2.2, it works just because of Newtonsoft.Json. In ASP.NET Core Version >= 3.0 it was replaced by System.Text.Json. Here is more about System.Text.Json, the new default ASP.NET Core JSON Serializer.
If you’d like to switch back to the previous default of using Newtonsoft.Json, do the following:
First Install the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package. Then in ConfigureServices() add a call to AddNewtonsoftJson() as follows:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddControllers()
.AddNewtonsoftJson()
...
}
For more details: ASP.NET Core 3.0 - New JSON serialization
Although you can change the default serializer and return to the previous functionality Newtonsoft.Json as mentioned by #TanvirArjel, the ideal is to use the default serializer System.Text as it has better performance. You can check it here: How to migrate from Newtonsoft.Json to System.Text.Json The error is caused due to the fact that the Heading class does not have a constructor valid without arguments which should be defined as follows.
public class Heading {
Heading(){
}
public string AttrOne {get; set;}
public string AttrTwo {get; set;}
}
From the docs:
Complex types
A complex type must have a public default constructor and public writable properties to bind. When model binding occurs, the class is instantiated using the public default constructor.
You will need to add a parameter-less constructor for binding to use that model.
The behavior may have worked for you before (2.2 and prior) if you were using NewtonsoftJson which may have allowed parameter-less constructor models. Since 3.0 .NET Core uses the newer System.Text.Json serializer by default.

How to share a View Component in a dll-referenced Razor Class Library?

ASP.NET Core 2.2.0
I built a RCL with some (Razor) pages, interfaces, repositories and models and I want to share that RCL using a DLL reference with my other projects. That works fine (using this) and now I want to use the View Components inside the RCL, but it gives me the error:
InvalidOperationException: Unable to resolve service for type 'Shared.ModelInterfaces.IMyRepository' while attempting to activate 'Shared.Components.ItemViewComponent'.
Diving deeper in the error, I found this:
method may only be called on a type for which type.is generic parameter is true
And it looks like this is causing the main error.
My ViewComponent has no Generic Type:
namespace Shared.Components
{
public class ItemViewComponent : ViewComponent
{
private readonly IMyRepository _myRepository;
public ItemViewComponent(IMyRepository myRepository)
{
_myRepository = myRepository;
}
public IViewComponentResult Invoke(string ViewType, string Category = "", string Organization = "", string ItemID = "")
{
// some code / some calls to my _myRepository / some returns
}
}
}
How can I fix this? I need the IMyRepository...
Side note
I know that RCLs usually are referenced by project or as a NuGet Package, but these methods have some disadvantages for me, that's why I reference my RCL by DLL.
You have to register your IMyRepository service in the project using the view component:
services.AddScoped<IMyRespository, MyRepository>();

Case-Insensitive Property Binding Without Using JsonProperty in .NET Core

I have a C# ASP.NET Core Web Api app and C# ASP.NET Core MVC app
In this case, the MVC app calls the API app with a model like this (the client was generated via AutoRest):
var thing = await thingApi.ThingPost(new ThingModel { ThingID = ThingID });
At this point, I can debug to see that the ThingID is set with my value. When I debug the API app I can see that the property did not transfer over... property defined as such:
public long ThingID { get; set; }
However, when I decorate it with
[JsonProperty(PropertyName = "thingID")]
Then the value is bound. I am trying to figure out how I can have it bind without having to decorate it with that attribute. Some kind of a case-insensitive resolver or something?
I've tried using:
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
and
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver());
Without updating the client, both failed to resolve the property. The thing is that I don't want the client to change. It should be generated as it is now, but I want the value to be bound to the property regardless of the case in the client. How can I do this?
I found the culprit... my model inherited from a base class (which I probably should have mentioned in my question) and my base class was decorated with:
[DataContract]
which obviously forces you to specify each of the properties you want exposed... so a bit of a duh moment.

StructureMap and HTTP request-scoped services - why is my service created twice in a single scope?

I have an ASP.NET MVC application using StructureMap.
I have created a service called SecurityContext which has a static Current property. A simplified version looks like this:
public class SecurityContext : ISecurityContext
{
public bool MyProperty { get; private set; }
public static SecurityContext Current
{
get
{
return new SecurityContext() { MyProperty = true };
}
}
}
I've hooked this up in my StructureMap registry as follows:
For<ISecurityContext>().Use(() => SecurityContext.Current);
My understanding of this Linq expression overload of the Use method is that the returned concrete object is the same for the entire HTTP request scope.
However, I've set up a test case where my context interface is injected in two places, once in the controller's constructor and again using the SetterProperty attribute in the base class my view inherits from.
When debugging I observe the Current static method being hit twice so clearly my assumptions are wrong. Can anyone correct what I'm doing here? The reason I want this request-scoped is because I'm loading certain data into my context class from the database so I don't want this to happen multiple times for a given page load.
Thanks in advance.
The default lifecycle for a configuration is Transient, thus each request for an ISecurityContext will create a new instance of SecurityContext. What I think you want is to use the legacy HttpContext lifecycle.
Include the StructureMap.Web nuget package. Then change your configuration to the following:
For<ISecurityContext>()
.Use(() => SecurityContext.Current)
.LifeCycleIs<HttpContextLifecycle>();
More information on lifecyles can be found here.
The HttpContextLifecycle is obsolete, however I do not know if or when it will be removed. The StructureMap team does recommend against using this older ASP.Net lifecycle. They state in the documentation that most modern web frameworks use a nested container per request to accomplish the same scoping. Information about nested containers can be found here.
I don't know if the version of ASP.Net MVC you are using is considered a modern web framework. I doubt it is because ASP.Net Core 1.0 is the really the first in the ASP.Net line to fully embrace the use of DI. However, I will defer to #jeremydmiller on this one.

Categories

Resources