0
votes

My classes:

    public abstract class BaseClass
    {
        public string Id { get; set; }
    }

    public abstract class BaseItemTraduisible : BaseClass
    {
        public string Code { get; set; }

    }

    public abstract class BaseTrad<TypeReference> : BaseClass
    {
        public string Libelle { get; set; }

        public string IdReference { get; set; }

        public TypeReference ObjReference { get; set; }

    }

    public class BaseTradVM<Item,Trad>
        where Item : BaseItemTraduisible
        where Trad : BaseTrad<Item>
    {

        public List<Item> Items { get; set; }

        public List<Trad> Trads { get; set; }

        public List<Langue> Langues { get; set; }

        public Item ItemVide { get; set; }

        public Trad TradVide { get; set; }

        public string Titre { get; set; }
    }

    public class CorpsDeMetierVM : BaseTradVM<CorpsDeMetier, CorpsDeMetierTrad>
    {

    }


    public partial class CorpsDeMetier : BaseItemTraduisible
    {
        public IList<CorpsDeMetierTrad> CorpsDeMetierTrads  { get; set; }

    }

    public partial class CorpsDeMetierTrad : BaseTrad<CorpsDeMetier>
    {


    }

My controller :

        // GET: CorpsDeMetiers
    public async Task<IActionResult> Index()
    {
        
        var cdmvm = new CorpsDeMetierVM();

        cdmvm.Items = _context.CorpsDeMetiers.Include(c => c.CorpsDeMetierTrads).ToList();
        cdmvm.Trads = _context.CorpsDeMetierTrads.Include(c => c.Langue).ToList();
        cdmvm.Langues = _context.Langues.OrderBy(l => l.Ordre).ToList();
        cdmvm.ItemVide = new CorpsDeMetier();
        cdmvm.TradVide = new CorpsDeMetierTrad();
        cdmvm.Titre = "LibelleCorpsDeMetier";

        return View("Views/ConfigurationTrad/index.cshtml", cdmvm);
    }

My view get the model like that :

   @model ArchiEnLigne.Models.BaseTradVM<BaseItemTraduisible,BaseTrad<BaseItemTraduisible>>

The error message :

InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'ArchiEnLigne.Models.CorpsDeMetierVM', but this ViewDataDictionary instance requires a model item of type 'ArchiEnLigne.Models.BaseTradVM``2[ArchiEnLigne.Data.Entities.BaseItemTraduisible,ArchiEnLigne.Data.Entities.BaseTrad``1[ArchiEnLigne.Data.Entities.BaseItemTraduisible]]'.

StackTrace:

´´´

Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary.EnsureCompatible(object value) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary..ctor(ViewDataDictionary source, object model, Type declaredModelType) lambda_method(Closure , ViewDataDictionary ) Microsoft.AspNetCore.Mvc.Razor.RazorPagePropertyActivator.CreateViewDataDictionary(ViewContext context) Microsoft.AspNetCore.Mvc.Razor.RazorPagePropertyActivator.Activate(object page, ViewContext context) Microsoft.AspNetCore.Mvc.Razor.RazorPageActivator.Activate(IRazorPage page, ViewContext context) Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context) Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, bool invokeViewStarts) Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable statusCode) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable statusCode) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, string contentType, Nullable statusCode) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync(ActionContext context, ViewResult result) Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync(ActionContext context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0<TFilter, TFilterAsync>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters() Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync() Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext) Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext) Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

´´´

I don't know why it can't make the conversion when the hierarchy is correct to me

I hope someone can help me

Thx for read me

1
Please post the full StackTrace of the exception. The error isn't in the code you posted but with your ASP.NET MVC view file's @model declaration.Dai
Tip: don't use base-classes with view-models. Is there a reason you can't use composition instead?Dai
CorpsDeMetier does not inherit from BaseTradVM<BaseItemTraduisible,BaseTrad<BaseItemTraduisible>>. I beleive you wanted to use CorpsDeMetierVM instead of CorpsDeMetierNikita Chayka
I little edit my message. I really need to use base-classes with my view-models cause i want to use a single view for a lot of view-model.Thibaud Rosado
@Dai i added StackTradeThibaud Rosado

1 Answers

0
votes

Your problem is related to covariance & contravariance. In some cases we can have a solution by declaring the view model abstract as interface instead of using abstract class (so that we can use covariance). The covariant interface can only expose get-only properties. So in this case I think you can change to that to keep your current design and I think it's reasonable because usually the ViewModel class should have readonly properties (once loaded). That prevents the properties from being accidentally modified after loaded (such as in the view scope). That makes sense and we can adapt to that design. The data instead can be loaded via the constructor (once).

Here is my proposed design with the most minimal change applied to your current design. Note that due to the constraint where Trad : BaseTrad<Item> which requires the BaseTrad to be an interface as well (cascading the variance of Item). So basically you need to turn 3 classes into 3 interfaces. I would use interface without hesitation but not sure if it's applicable in your case. However I'm pretty sure that this is the only solution if you want to keep your current generic design. The logic can be maintained as much as possible, by changing your current generic design, a lot of code will be changed as well.

The 3 interfaces you need are as follows (Note about using the keyword out and some properties being changed to get-only):

//refactored from BaseClass
public interface IBaseClass
{
    string Id { get; set; }
}

//refactored from BaseTrad
public interface IBaseTrad<out TypeReference> : IBaseClass
{
    string Libelle { get; set; }

    string IdReference { get; set; }

    TypeReference ObjReference { get; }
}

//refactored from BaseTradVM
public interface IBaseTradVM<out Item,out Trad>
    where Item : BaseItemTraduisible
    where Trad : IBaseTrad<Item>
{
    //we need IEnumerable here to support covariance
    IEnumerable<Item> Items { get; }

    IEnumerable<Trad> Trads { get; }

    List<Langue> Langues { get; set; }

    Item ItemVide { get; }

    Trad TradVide { get; }

    string Titre { get; set; }
}

Now you can define your abstract class BaseTradVM implementing IIBaseTradVM so that other classes can inherit from BaseTradVM without having to re-implement the IBaseTradVM:

public abstract class BaseTradVM<Item,Trad> : IBaseTradVM<Item,Trad> 
    where Item : BaseItemTraduisible
    where Trad : IBaseTrad<Item>
{
    public BaseTradVN(IEnumerable<Item> items, 
                      IEnumerable<Trad> trads,
                      Item itemVide, 
                      Trad tradVide) {
       Items = items;
       Trads = trads;
       ItemVide = itemVide;
       TradVide = tradVide;
    }

    public IEnumerable<Item> Items { get; }

    public IEnumerable<Trad> Trads { get; }

    public List<Langue> Langues { get; set; }

    public Item ItemVide { get; }

    public Trad TradVide { get; }

    public string Titre { get; set; }
}

//similarly for BaseTrad
public abstract class BaseTrad<TypeReference> : IBaseTrad<TypeReference> {
    public BaseTrad(TypeReference typeReference) {
        ObjReference = typeReference;
    }
    public string Libelle { get; set; }

    public string IdReference { get; set; }

    public TypeReference ObjReference { get; }
}

The other classes adjusted after refactoring into interfaces:

public class CorpsDeMetierVM : BaseTradVM<CorpsDeMetier, CorpsDeMetierTrad>
{
    public CorpsDeMetierVM(IEnumerable<CorpsDeMetier> items, 
                           IEnumerable<CorpsDeMetierTrad> trads,
                           CorpsDeMetier itemVide, 
                           CorpsDeMetierTrad tradVide) : base(items, trads, itemVide, tradVide) {
    }
}

public partial class CorpsDeMetierTrad : BaseTrad<CorpsDeMetier>
{
    public CorpsDeMetierTrad(CorpsDeMetier typeReference) : base(typeReference){}

}

Now we've almost done. Just update the way you load data into your view model. Of course update the @model declaration for your view as well (change class to interface):

var items = _context.CorpsDeMetiers.Include(c => c.CorpsDeMetierTrads).ToList();
var trads = _context.CorpsDeMetierTrads.Include(c => c.Langue).ToList();
var itemVide = new CorpsDeMetier();
var tradVide = new CorpsDeMetierTrad();
var cdmvm = new CorpsDeMetierVM(items, trads, itemVide, tradVide);

cdmvm.Langues = _context.Langues.OrderBy(l => l.Ordre).ToList();
cdmvm.Titre = "LibelleCorpsDeMetier";

The view's model should be updated to this:

@model ArchiEnLigne.Models.IBaseTradVM<BaseItemTraduisible,BaseTrad<BaseItemTraduisible>>

That's all. It now should work when passed to your view.

NOTE: Because of refactoring to the classes (added with constructor), some of your code (not shown) may have to be updated as well. Finally the constructors are not required. If you somehow cannot declare a constructor, you can expose some public method in the concrete class to set the public get-only properties instead (but those properties then must be private set instead of readonly as in my code above). That's of course not very beautiful.