Having issues mapping from a simple list to a corresponding property in a ViewDto - collections

I have a collection of "Alerts" (List) that I need to map to a property in a more complex class.
My destination class hierarchy is as follows:
public class BaseReplyDto<T> where T : class, new()
{
public List<ErrorType> Errors { get; set; }
public T ReplyData { get; set; }
}
public class GetAllAlertsReplyViewDto : BaseReplyDto<List<Alert>>{}
My Mapper configuration is this:
Mapper.CreateMap<List<Alert>, GetAllAlertsReplyViewDto>()
.ForMember(dest => dest.Errors, opt => opt.Ignore())
;
When I run the app, I get a mapper validation error:
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: AutoMapper.AutoMapperConfigurationException:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
List`1 -> GetAllAlertsReplyViewDto (Destination member list)
System.Collections.Generic.List`1[[bioSynq.Infrastructure.Entities.Concrete.Alert, bioSynq.Infrastructure.Entities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] -> bioSynq.Infrastructure.ViewDtos.DisplayAlertsArea.GetAllAlertsReplyViewDto (Destination member list)
ReplyData
Basically, it's telling me I need to provide some configuration to map my list of Alerts into the list of alerts that is my ReplyData property.
I've mucked around with about twenty different versions of syntax (.ForMember, .ForSourceMember, etc), but can't find the right syntax to get what seems to be a very simple mapping of one list into another.
Anyone know the correct syntax to do this?

In your mapping configuration I don't see configuration for property public List<Alert> ReplyData { get; set; } from GetAllAlertsReplyViewDto class. My guess is that you need something like this
Mapper.CreateMap<List<Alert>, GetAllAlertsReplyViewDto>()
.ForMember(dest => dest.Errors, opt => opt.Ignore())
.ForMember(dest => dest.ReplyData , opt => opt.MapFrom(list => list));
Hope it'll help.

Related

Unable to access the Roles from Authorize Attribute in IActionModelConvention implemented Apply Method

I am trying here to parse the Roles from Action Method Applied Authorize filter. But I am only able to get in Runtime through a quick watch. But Unable to get it via LINQ query.
internal class AuthorizeModelConvention : IActionModelConvention
{
public void Apply(ActionModel action)
{
IReadOnlyList<object> actionAttributes = action.Attributes;
if (!actionAttributes.Select(x => x.GetType()).Any(x => x.Name.Contains("AllowAnonymousAttribute")))
{
action.Filters.Add(new AuthorizeFilter("auth-policy"));
}
}
}
According to your description, I suggest you could try to use below codes to check if the authorize attribute's roles value is "admin" or not.
actionAttributes.Select(t => t.GetType().GetProperties().ToList().Select(x => x.GetValue(t, null)).ToList()).Any(x => x.Contains("admin"));
Result:

ASP.NET Core view location ignore case on linux

Controller:
[Area("cp"), Route("[area]/[controller]"), Authorize]
public class AuthController: Controller
Action:
[Route("login"), AllowAnonymous]
public async Task<IActionResult> Login() {return View();}
Real view location is areas\cp\auth\login.cshtml (lowercase)
I found that on linux razor engine trying to find path like this areas\cp\Auth\Login.cshtml (Upper letter)
And result of action is 404 NotFound.
All working find on windows.
An unhandled exception has occurred while executing the request.
System.InvalidOperationException: The view 'Login' was not found. The
following locations were searched:
/areas/cp/views/Auth/Login.cshtml <--- there is view exist
/Areas/cp/Views/Auth/Login.cshtml
/Areas/cp/Views/Shared/Login.cshtml
/Views/Shared/Login.cshtml
/Pages/Shared/Login.cshtml
Enabling lowercaseurls not helped
services.AddRouting(options => options.LowercaseUrls = true);
Adding custom IViewLocationExpander too
public class ViewLocationExpander : IViewLocationExpander
{
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
//{2} is area, {1} is controller,{0} is the action
var locations = new[]
{
"areas/{2}/views/{1}/{0}.cshtml".ToLower(),
};
return locations.Union(viewLocations); //Add mvc default locations after ours
}
public void PopulateValues(ViewLocationExpanderContext context)
{
context.Values["customviewlocation"] = nameof(ViewLocationExpander);
}
}
How can i solve this? I dont want to rename my locations to match with upper letter
UPD:
I think that is the best solution for me is get implementation RazorViewEngine from asp.net git repo and add some ToLower() cases to FindView() and GetView() and register it like default engine. But is it ok about license?

Is it possible to use ICacheManager<> with different type of configurations at the same time?

Imagine that I have interfaces like below which all inherits from ICacheManager<>
public interface ICacheManagerRuntime<T> : ICacheManager<T>
public interface ICacheManagerRedis<T> : ICacheManager<T>
public interface ICacheManagerRedisWithRuntime<T> : ICacheManager<T>
I want to inject ICacheManager{CacheType} interfaces to implemantation of Cache classes like:
CacheRuntime, CacheRedis, CacheRedisWithRuntime
With unity I want to inject them like below:
container.RegisterType<ICacheManagerRuntime<object>>(
new ContainerControlledLifetimeManager(),
new InjectionFactory((c, t, n) =>
{
return CacheFactory.Build... // Return CacheManager just with RuntimeCacheHandle
})));
container.RegisterType<ICacheManagerRedis<object>>(
new ContainerControlledLifetimeManager(),
new InjectionFactory((c, t, n) =>
{
return CacheFactory.Build... // Return CacheManager just with RedisCacheHandle
})));
container.RegisterType<ICacheManagerRedisWithRuntime<object>>(
new ContainerControlledLifetimeManager(),
{
return CacheFactory.Build... // Return CacheManager just with RuntimeCacheHandleWithRedisBackPlane
})));
What ever I have done, I am getting this exception:
An unhandled exception of type 'Microsoft.Practices.Unity.ResolutionFailedException' occurred in Microsoft.Practices.Unity.dll
Additional information: Resolution of the dependency failed, type = "Solid.Play.Business.Interfaces.IProductService", name = "(none)".
Exception occurred while: Resolving parameter "cache" of constructor Solid.Play.Cache.Caches.CacheRuntime(Solid.Play.Cache.Interfaces.ICacheManagerRuntime`1[[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] cache).
Exception is: InvalidCastException - Unable to cast object of type 'CacheManager.Core.BaseCacheManager`1[System.Object]' to type 'Solid.Play.Cache.Interfaces.ICacheManagerRuntime`1[System.Object]'.
-----------------------------------------------
At the time of the exception, the container was:
Resolving Solid.Play.Business.Services.ProductService,(none) (mapped from Solid.Play.Business.Interfaces.IProductService, (none))
Resolving Solid.Play.Cache.Interception.CachingInterceptorBehavior,(none)
Resolving parameter "cache" of constructor Solid.Play.Cache.Interception.CachingInterceptorBehavior(Solid.Play.Cache.Interfaces.ICacheSolid cache)
Resolving Solid.Play.Cache.Caches.CacheSolid,(none) (mapped from Solid.Play.Cache.Interfaces.ICacheSolid, (none))
Resolving parameter "cacheRuntime" of constructor Solid.Play.Cache.Caches.CacheSolid(Solid.Play.Cache.Interfaces.ICacheRuntime cacheRuntime, Solid.Play.Cache.Interfaces.ICacheRedis cacheRedis, Solid.Play.Cache.Interfaces.ICacheRedisWithRuntime cacheRedisWithRuntime)
Resolving Solid.Play.Cache.Caches.CacheRuntime,(none) (mapped from Solid.Play.Cache.Interfaces.ICacheRuntime, (none))
Resolving parameter "cache" of constructor Solid.Play.Cache.Caches.CacheRuntime(Solid.Play.Cache.Interfaces.ICacheManagerRuntime`1[[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] cache)
The cast does not work because you are trying to cast an instance to an interface it does not implement.
Simplified, what you are trying looks like this:
public interface IBase
{
}
public interface ISub : IBase { }
public class BaseClass : IBase
{
}
var sub = (ISub)new BaseClass();
If you want to inject different kind of instances of the same interfaces, the Unity DI framework provides a way to do that via named injection.
Example:
container.RegisterType<ICacheManager<object>>("runtimeCache",
new ContainerControlledLifetimeManager(),
new InjectionFactory((c, t, n) =>
{
return CacheFactory.Build<object>(s =>
{
s.WithSystemRuntimeCacheHandle("cache.runtime");
});
}));
container.RegisterType<ICacheManager<object>>("redisCache",
new ContainerControlledLifetimeManager(),
new InjectionFactory((c, t, n) =>
{
return CacheFactory.Build<object>(s =>
{
s.WithRedisConfiguration("cache.redis", config =>
{
config
.WithAllowAdmin()
.WithDatabase(0)
.WithEndpoint("localhost", 6379);
})
.WithRedisCacheHandle("cache.redis");
});
}));
To resolve the first one, you'd use
var runtimeCache = container.Resolve<ICacheManager<object>>("runtimeCache");
You can inject the ICacheManager interface to constructors with attributes for example.
public YourClass([Dependency("runtimeCache")] ICacheManager<object> cache)
{
}

How to add custom ClientValidationRules (unobtrusive validation) for a complex type on a model?

Say I have a custom validation attribute ValidateFooIsCompatibleWith model like so:
public class FooPart
{
public string Foo { get; set; }
public string Eey { get; set; }
}
public class FooableViewModel
{
public FooPart Foo1 { get; set; }
[ValidateFooIsCompatibleWith("Foo1")]
public FooPart Foo2 { get; set; }
}
Let's say I also have custom EditorTemplates defined for FooPart:
#Html.TextBoxFor(m => m.Foo)
#Html.TextBoxFor(m => m.Eey)
And thus my view is essentially:
#Html.EditorFor(m => m.Foo1)
#Html.EditorFor(m => m.Foo2)
Server side, the validation works fine. However, no matter what I try, I can't get the rendered html to add the rule.
If I implement IClientValidatable, it turns out that GetClientValidationRules() never gets called. (I have successfully used IClientValidatable with "simple" fields before).
I also tried registering my own custom adapter by inheriting from DataAnnotationsModelValidator<TAttribute> and registering it in the global.asax with DataAnnotationsModelValidatorProvider.RegisterAdapter(...) That approach too fails to call GetClientValidationRules().
** Update **
If a add both a custom ModelMetadataProvider and a custom ModelValidatorProvider so that I can set breakpoints, I notice an interesting bit of behavior:
a request is made to the ModelMetadataProvider for metadata with a ContainerType of FooableViewModel and a ModelType of FooPart. However, no corresponding request is made to the ModelValidatorProvider, so I can't insert my custom client validation rules there.
requests are made to the ModelValidatorProvider with a ContainerType of FooPart and a ModelType of string for both the Foo and Eey properties. But at this level, I don't know the attributes applied to the FooPart property.
How can I get the MVC framework to register my custom client validation rules for complex types?
I found a solution:
First, Create a custom model metadata provider (see https://stackoverflow.com/a/20983571/24954) that checks the attributes on the complex type, and stores a client validatable rule factory in the AdditionalValues collection, e.g. in the CreateMetadataProtoype override of the CachedDataAnnotationsModelMetadataProvider
var ruleFactories = new List<Func<ModelMetadata, ControllerContext, IEnumerable<ModelClientValidationRules>>>();
...
var clientValidatable = (IClientValidatable)attribute;
ruleFactories.Add(clientValidatable.GetClientValidationRules);
...
result.AdditionalValues.Add("mycachekey", ruleFactories);
Next, register this as the default metadata provider in the global.asax
protected void Application_Start()
{
ModelMetadataProviders.Current = new MyCustomModelMetadataProvider();
....
}
Then I created an html helper that would process the modelmetata and create/merge the "data-val*" html attributes from each of AdditionalValues collection.
public static IDictionary<string, Object> MergeHtmlAttributes<TModel>(this HtmlHelper<TModel>, object htmlAttributes = null)
{
var attributesDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
//ensure data dictionary has unobtrusive validation enabled for the element
attributesDictionary.Add("data-val", "true");
//loop through all the rule factories, and execute each factory to get all the rules
var rules = ruleFactory(helper.Html.ViewData.ModelMetadata, helper.Html.ViewContext);
//loop through and execute all rules in the ruleFactory collection in the AdditionalValues
//and add the data-val attributes for those.
attributesDictionary.add("data-val-" + rule.ValidationType, ruleErrorMessage);
//similarly for anything in the rule.ValidationParameters
attributesDictionary.Add("data-val-" + rule.ValidationType + "-" + parameterName, parameterValue);
}
Finally, in my editor template, call the html helper (which has a model type of `FooPart1) for each complex type property, e.g.
#Html.TextBoxFor(m => m.Foo, Html.MergeHtmlAttributes(new { #class="Bar"}))
#Html.TextBoxFor(m => m.Eey, Html.MergeHtmlAttributes())
I actually ended up creating a second interface (with the same signature as IClientValidatable) that allowed me to customize rules (primarily for error messages) for the individual fields of a complex type. I also extended the helper to take a string argument that could be formatted with my custom rules.
jQuery.validator.setDefaults({
success: "valid"
});
$( "#foo" ).validate({
rules:
{
rule1: {required: true, min: 3},
parent:
{
required: function(element) {return $("#age").val() < 13;}
}
}
});
Complex types seem to hassle me for no good reason so try the Jquery validator. Depending on what you're trying to validate it might get the job done.

Entity to Model and foreign key objects

I have an EF object called SportDivision. For simplicity's sake, I won't include every field, just the ones that are relevant:
[Table("SportDivision", Schema = "dbo")]
public class SportDivision: BaseReferenceEntity
{
public int Id { get; set; }
public string Name { get; set; }
public int SportId { get; set; }
[ForeignKey("SportId")]
public virtual Sport Sport { get; set; }
}
So it has a SportId and it's a foreign key that points to the table Sport.
Now, I can't just use an EF object in my views, so I have a model class that's mapped to SportDivision called SportDivisionModel:
public class SportDivisionModel: BaseReferenceModel
{
public int Id { get; set; }
public string Name { get; set; }
public int SportId { get; set; }
//Read only fields
public string Sport { get; set; }
}
I use automapper to transfer data from SportDivision to SportDivisionModel and vice versa. The mapping looks like this:
Mapper.CreateMap<SportDivision, SportDivisionModel>()
.ForMember(x => x.Sport, c => c.MapFrom(e => e.Sport.Name));
Mapper.CreateMap<SportDivisionModel, SportDivision>();
And I have a genericized service that CRUDs and translates data from entity to model or model to entity. Everything works fine except on Create, of which the function is shown below:
public TModel Create<TModel, TEntity>(TModel entry)
where TModel : BaseReferenceModel
where TEntity : BaseReferenceEntity
{
var dm = ServiceLocator.Current.GetInstance<ICrudService<TEntity>>();
var raw = Mapper.Map<TModel, TEntity>(entry);
var created = dm.CreateOrUpdate(raw);
return Mapper.Map<TEntity, TModel>(dm.FindById(created.Id));
}
In the very last line, where you see dm.FindById(created.Id), it returns a SportDivisionModel object with no Sport name. A null reference exception is found in .ForMember(x => x.Sport, c => c.MapFrom(e => e.Sport.Name));. It didn't load Sport after the entry was just created in the database.
I've debugged the code, and I see that the entry with a valid SportId is entered into the SportDivision table of my database, but when I try and bring it over to my MVC application, it doesn't get all the information.
This only is an issue on create. If I simply get data from the database without creating it beforehand, or if I edit the information, then the Sport field in my model object does get populated. I don't know why this is happening, and I can't use the .Include in my generic service call (because not all BaseReferenceEntity classes have a foreign key pointing to Sport).
Please advise. Thanks in advance.
I must play Sherlock Holmes and try to derive what could be the content of CreateOrUpdate and FindById from the indications in your question:
You say that you don't use Include because of the generic service. I assume that you also don't use explicit loading (Load) because you would face the same problem that you cannot really make it generic.
Conclusion: Because the Sport navigation property in the SportDivision gets loaded in certain scenarios (Edit) this can only happen due to lazy loading. The conclusion is backed by the fact that the Sport property is marked as virtual.
Lazy loading relies on proxies. If your SportDivision entity is a proxy then
either loading the Sport entity works
or you get an exception telling you that the context is already disposed (if you have disposed the context)
Number 2 is not the case -> Conclusion: Number 1 must be the case if the pre-condition is fulfilled
But Number 1 also isn't the case (loading Sport does not work)
Conclusion: The pre-condition that your SportDivision entity is a proxy is not true.
So: SportDivision is not a proxy. Could this mean that you have lazy loading in the context disabled? No: Because you are saying that editing works it means that when you load entities from the database they are loaded as proxies and support lazy loading.
Editing works, lazy loading isn't disabled but creating a new entity does not work in the way that the Sport entity is loaded when you proceed to use the newly created entity.
Conclusion: Your newly created entity (returned from CreateOrUpdate) is not a proxy and CreateOrUpdate looks similar to this:
public TEntity CreateOrUpdate(TEntity raw) where TEntity : class
{
if (blabla)
; //update
else
{
context.Set<TEntity>().Add(raw);
context.SaveChanges();
return raw;
}
}
and FindById is just:
public TEntity FindById(int id)
{
return context.Set<TEntity>().Find(id);
}
Since you are passing raw directly into the Add method of the DbSet<T> the question raises where does raw come from and how is it created.
Obviously AutoMapper creates the entity after this line: var raw = Mapper.Map<TModel, TEntity>(entry);
How does Automapper create an entity? Probably by calling new TEntity or by using some reflection code like Activator.CreateInstance or...
It doesn't really matter how, but for sure AutoMapper doesn't instantiate an Entity Framework proxy which had to be created by:
var entity = context.Set<TEntity>().Create();
If all this is true, I feel totally screwed by AutoMapper and generic excesses. If all this wouldn't be generic we could solve the problem by:
context.Set<SportDivision>().Add(raw);
context.SaveChanges();
context.Entry(raw).Reference(r => r.Sport).Load();
Instead we must try some ugly tricks now:
context.Set<TEntity>().Add(raw);
context.SaveChanges();
context.Entry(raw).State = EntityState.Detached;
// We hope that raw is now really out of the context
raw = context.Set<TEntity>().Find(raw.Id);
// raw must be materialized as a new object -> Hurray! We have a proxy!
return raw;
(I'm really not sure if the Detached trick above does work. Aside from that you are forced to reload an entity from the database you just have created and saved which is stupid somehow.)
Potential trick number 2 (without reloading from DB but for the price of being a further step more ugly):
context.Set<TEntity>().Add(raw);
context.SaveChanges();
context.Entry(raw).State = EntityState.Detached;
// We hope that raw is now really out of the context
var anotherRaw = context.Set<TEntity>().Create(); // Proxy!
anotherRaw.Id = raw.Id;
context.Set<TEntity>().Attach(anotherRaw);
context.Entry(anotherRaw).CurrentValues.SetValues(raw);
context.Entry(anotherRaw).State = EntityState.Unchanged;
return anotherRaw; // Proxy! Lazy loading will work!
Does AutoMapper have a feature of a "custom allocator or instantiator" and can custom user data (a context) be supplied? Then there would be a chance to let AutoMapper call context.Set<TEntity>().Create();. Or is it possible to instantiate the object by hand, pass it to AutoMapper and AutoMapper just updates the object's properties?
BTW: The line...
context.Entry(anotherRaw).CurrentValues.SetValues(raw);
...is kind of EF's built-in "AutoMapper". The parameter of SetValues is a general System.Object (could be your ...Model object) and the method maps property values from the supplied object to properties of attached entities by identical property names. Maybe you can leverage this feature somehow instead of using the mapping from model to entity done by AutoMapper.

Resources