Changing the output DTO (return value) of the GetAll method in an AsyncCrudAppService - asp.net-mvc

I'm using ABP's AsyncCrudAppService in my AppServices. Here's my interface:
public interface IAssetRequisitionAppService : IAsyncCrudAppService
<AssetRequisitionDto, Guid, GetAllInput, AssetRequisitionDto, AssetRequisitionDto, AssetRequisitionDetailsDto>
{ }
And the service:
public class AssetRequisitionAppService : AsyncCrudAppService
<AssetRequisition, AssetRequisitionDto, Guid, GetAllInput, AssetRequisitionDto, AssetRequisitionDto, AssetRequisitionDetailsDto>,
IAssetRequisitionAppService
{
public AssetRequisitionAppService(IRepository<AssetRequisition, Guid> repository) : base(repository)
{ }
}
Now, I believe all these standard CRUD methods will return the default type (which is AssetRequisitionDto in my case). But, what I want to do is to return a different type for Get() and GetAll() methods.
Get() should have a much more detailed DTO with subproperties of the Navigation props. But GetAll() should have a much less detailed one just to populate a table.
Is there a way to override the return types in some way?

Yes, there is some way.
// using Microsoft.AspNetCore.Mvc;
[ActionName(nameof(GetAll))]
public PagedResultRequestDto MyGetAll(PagedResultRequestDto input)
{
return input;
}
[NonAction]
public override Task<PagedResultDto<UserDto>> GetAll(PagedResultRequestDto input)
{
return base.GetAll(input);
}
Reference: https://github.com/aspnetboilerplate/aspnetboilerplate/issues/2859

Well I noticed I'll eventually need more complex filtering methods anyway. So, I created my custom types and methods.
First, I created my own GetAllInput derived from PagedAndSortedResultRequestDto. It's suited for most of my services, as I usually need to query the data related to employees and locations:
public class GetAllInput : PagedAndSortedResultRequestDto
{
public long? PersonId { get; set; }
public long? LocationId { get; set; }
public EEmployeeType? EmployeeType { get; set; }
}
After that I wrote a GetAll method for each of my services. They all return a PagedResultDto<> so I can use it's functionalities in presentation layer. Here's one example below:
//MovableAppService
public PagedResultDto<MovableLineDto> GetAllLinesRelatedToUser(GetAllInput input)
{
Logger.Info("Loading all movables related to current user");
IQueryable<Movable> queryable = null;
if (input.PersonId.HasValue)
{
if (input.EmployeeType == EEmployeeType.Recorder)
{
var recorder = _personRepository.GetAll()
.OfType<Employee>()
.FirstOrDefault(x => x.Id == input.PersonId);
var authorizedStorageIds = recorder.StoragesAuthorized.Select(y => y.Id);
queryable = _repository.GetAll()
.Where(x => authorizedStorageIds.Contains(x.StorageOfOriginId));
}
else if (input.EmployeeType == EEmployeeType.UnitManager)
{
var locationCodes = _locationRepository.GetAll()
.Where(x => x.ManagerInChargeId == input.PersonId)
.Select(x => x.Code);
foreach (var code in locationCodes)
{
queryable = _locationRepository.GetAll()
.Where(x => x.Code.StartsWith(code))
.SelectMany(x => x.AssignmentDocs)
.SelectMany(x => x.Movements)
.OfType<Assignment>()
.Where(x => x.TimeOfReturn == null)
.Select(x => x.Asset)
.OfType<Movable>();
queryable = queryable.Concat(queryable);
}
}
else if (input.TenantIdsOversee.Count() > 0)
{
var overseer = _personRepository.GetAll()
.OfType<Overseer>()
.FirstOrDefault(x => x.Id == input.PersonId);
var overseeingTenantIds = overseer.TenantsOversee.Select(y => y.Id);
queryable = _repository.GetAll()
.Where(x => overseeingTenantIds.Contains((int)x.TenantId));
}
else if (input.EmployeeType == EEmployeeType.Employee)
{
queryable = _personRepository.GetAll()
.OfType<Employee>()
.Where(x => x.Id == input.PersonId)
.SelectMany(x => x.AssignmentDocs)
.SelectMany(x => x.Movements)
.OfType<Assignment>()
.Where(x => x.TimeOfReturn == null)
.Select(x => x.Asset)
.OfType<Movable>();
}
}
var list = queryable.ToList()
.OrderBy(x => x.Category.Definition);
var items = _objectMapper.Map<IReadOnlyList<MovableLineDto>>(list);
return new PagedResultDto<MovableLineDto>(items.Count, items);
}
Btw, aaron's answer is probably valid for ASP.NET Core projects. But my project is in MVC EF6, so those annotations are not available for me.
Now I'm marking this as the answer but if there's a more elegant way, I'm happy to see and I'll change my mark then.

Related

How to get all action , controller and area names while running asp core 3.1

i have an asp.net core 3.1 application and i want to get all controller , action and area names when my application is running like get action names with reflection in mvc.
Is there any way ?
Try this:
1.Model:
public class ControllerActions
{
public string Controller { get; set; }
public string Action { get; set; }
public string Area { get; set; }
}
2.Display the Controller,Action and Area Name:
[HttpGet]
public List<ControllerActions> Index()
{
Assembly asm = Assembly.GetExecutingAssembly();
var controlleractionlist = asm.GetTypes()
.Where(type => typeof(Controller).IsAssignableFrom(type))
.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
.Select(x => new
{
Controller = x.DeclaringType.Name,
Action = x.Name,
Area = x.DeclaringType.CustomAttributes.Where(c => c.AttributeType == typeof(AreaAttribute))
}).ToList();
var list = new List<ControllerActions>();
foreach (var item in controlleractionlist)
{
if (item.Area.Count() != 0)
{
list.Add(new ControllerActions()
{
Controller = item.Controller,
Action = item.Action,
Area = item.Area.Select(v => v.ConstructorArguments[0].Value.ToString()).FirstOrDefault()
});
}
else
{
list.Add(new ControllerActions()
{
Controller = item.Controller,
Action = item.Action,
Area = null,
});
}
}
return list;
}
In my application I didn't require finding areas, so I ended up creating a simplified version based on the accepted answer. If it's useful to anyone else, here it is:
public static class ControllerActionEnumerator
{
public static List<ControllerAndItsActions> GetAllControllersAndTheirActions()
{
Assembly asm = Assembly.GetExecutingAssembly();
IEnumerable<Type> controllers = asm.GetTypes().Where(type => type.Name.EndsWith("Controller"));
var theList = new List<ControllerAndItsActions>();
foreach (Type curController in controllers)
{
List<string> actions = curController.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public)
.Where(m => m.CustomAttributes.Any(a => typeof(HttpMethodAttribute).IsAssignableFrom(a.AttributeType)))
.Select(x => x.Name)
.ToList();
theList.Add(new ControllerAndItsActions(curController.Name, actions));
}
return theList;
}
}
public class ControllerAndItsActions
{
public string Controller { get; }
public List<string> Actions { get; }
public ControllerAndItsActions(string controller, List<string> actions) => (Controller, Actions) = (controller, actions);
}
Try this:
ControllerFeature controllerFeature = new ControllerFeature();
this.ApplicationPartManager.PopulateFeature(controllerFeature);
IEnumerable<TypeInfo> typeInfos = controllerFeature.Controllers;
ApplicationPartManager must use DI to your class.
I wrote new code with a Controller and Action DisplayNames.
Assembly assembly = Assembly.GetExecutingAssembly();
var controllersList = assembly.GetTypes().Where(ctype => typeof(Controller).IsAssignableFrom(ctype)).Select(type => new { ty = type, methods = type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public).Where(y => Attribute.IsDefined(y, typeof(LogRequestAttribute))) }).Where(type => type.methods.Any(x => Attribute.IsDefined(x, typeof(LogRequestAttribute)))).Select(controller => new
{
AreaName = (controller.ty.GetCustomAttribute(typeof(AreaAttribute)) as AreaAttribute)?.RouteValue,
ControllerTitle = (controller.ty.GetCustomAttribute(typeof(DisplayNameAttribute)) as DisplayNameAttribute)?.DisplayName,
ControllerName = controller.ty.Name,
Actions = controller.methods.Select(action => new
{
ActionTitle = (action.GetCustomAttribute(typeof(DisplayNameAttribute)) as DisplayNameAttribute)?.DisplayName,
ActionName = action.Name,
}).ToList(),
}).ToList();
// group by on area
var areaList = controllersList.GroupBy(group => group.AreaName).Select(ar => new
{
AreaName = ar.Key,
Controllers = ar.Select(co => new
{
ControllerTitle = co.ControllerTitle,
ControllerName = co.ControllerName,
Actions = co.Actions,
}).ToList(),
}).ToList();

Authorization Policy using Dependency Injection in asp.net core

I want to create policy based authorisation for aspnet.core
I have lots of policies that I want to implement and I don't want to bloat the startup.cs file with policies.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options
.AddPolicy("Policy1", policyBuilder =>
{
policyBuilder
.RequireClaim("scope", "1")
.Build();
}).AddPolicy("Policy2", policyBuilder =>
{
policyBuilder
.RequireClaim("scope", "2")
.Build();
}).AddPolicy("Policy3", policyBuilder =>
{
policyBuilder
.RequireClaim("scope", "3")
.Build();
}).AddPolicy("Policy4", policyBuilder =>
{
policyBuilder
.RequireClaim("scope", "4")
.Build();
;
});
});
}
I want to inject all the policies at startup.
Does anyone have any idea how to do this?
I thought about creating a factory that gets all the policies by reflection, but I really want to be able to inject classes into the policies.
e.g.
public interface IPolicy
{
string Name { get; }
AuthorizationPolicy AuthorizationPolicy { get; }
}
public class UserCreatePolicy : IPolicy
{
public const string Name = "UserCreatePolicy";
public UserCreatePolicy(IUserRoleService userRoleService)
{
_userRoleService = userRoleService;
}
public AuthorizationPolicy AuthorizationPolicy =>
new AuthorizationPolicyBuilder()
.RequireClaim("scope", _userRoleService.GetRole(1))
.Build();
}
public class AuthorizationPolicyFactory
{
public static void Create(AuthorizationOptions authorizationOptions)
{
typeof(IPolicy).Assembly
.GetTypes()
.Where(x => typeof(IPolicy).IsAssignableFrom(x) && !x.IsAbstract && !x.IsInterface)
.ToList()
.ForEach(type =>
{
if (Activator.CreateInstance(type) is IPolicy policy)
{
authorizationOptions.AddPolicy(policy.Name, policy.AuthorizationPolicy);
}
});
}
}
Ideally I don't want to have to use reflection and instead use Dependency injection.
You could use the IServiceProvider directly. Even if it is an anti pattern for such cases I see no way around.
public interface IPolicy
{
string Name { get; }
void ConfigurePolicy(AuthorizationPolicyBuilder builder);
}
public class UserCreatePolicy : IPolicy
{
public string Name { get; } = "UserCreatePolicy";
public UserCreatePolicy(IUserRoleService userRoleService)
{
_userRoleService = userRoleService;
}
public void ConfigurePolicy(AuthorizationPolicyBuilder builder)
{
builder
.RequireClaim("scope", _userRoleService.GetRole(1))
.Build();
}
}
To configure this policies I've created an extension method.
public static class AuthorizationPolicyExtensions
{
public static void ConfigureAuthorizationPolicies(this IServiceCollection serviceCollection, AuthorizationOptions authorizationOptions)
{
var policiesTypes = typeof(IPolicy).Assembly
.GetTypes()
.Where(x => typeof(IPolicy).IsAssignableFrom(x) && !x.IsAbstract && !x.IsInterface)
.ToList();
foreach (var type in policiesTypes)
{
serviceCollection.AddTransient(type);
}
var serviceProvider = serviceCollection.BuildServiceProvider();
var policies = policiesTypes
.Select(x => serviceProvider.GetService(x) as IPolicy)
.Where(x => x != null);
foreach (var policy in policies)
{
authorizationOptions.AddPolicy(policy.Name, policy.ConfigurePolicy);
}
}
}
It searches for IPolicy types and registers them in the inversion of control container. Then it will resolve those IPolicy's with the IServiceProvider and register them.

Add multiple included dynamically

I put together some helpers that will allow me to register includes by type. Looks like this:
Dictionary<Type, LambdaExpression[]> includes =
new Dictionary<Type, LambdaExpression[]>();
I register includes like this:
public void Add<T>(params Expression<Func<T, object>>[] includes)
{
this.includes.Add(typeof(T), includes);
}
Add<Customer>(e =>
e.Address.City.Province.Country,
e.Products.Select(p => p.Category));
Notice there are two includes for Customer. I then have this method that gets includes by type:
DbSet<T> entitySet = null;
void LoadIncludes()
{
var includes = Includes.Instance.Get<T>().DirectCast<Expression<Func<T, object>>[]>();
if (includes != null)
{
foreach (var include in includes)
{
entitySet.Include(include).Load();
}
}
}
When getting my entity, I do this:
public T GetById(int id)
{
LoadIncludes();
return entitySet.SingleOrDefault(x => x.Id == id);
}
It works well, but it's so slow, and it's because of the .Load() method I am calling in LoadIncludes(). Is there a faster way to do what I want to do here?
You shouldn't call Load, but build and use a Queryable<T> with Include chain.
Replace LoadIncludes with private function:
private IQueryable<T> GetEntitySet()
{
var set = entitySet.AsQueryable();
var includes = Includes.Instance.Get<T>().DirectCast<Expression<Func<T, object>>[]>();
if (includes != null)
{
foreach (var include in includes)
{
set = set.Include(include);
}
}
return set;
}
and use it as follows:
public T GetById(int id)
{
return GetEntitySet().SingleOrDefault(x => x.Id == id);
}

Automapper - Create Object if src not null

sHow can I do something like this:
.ForMember(dest => dest.Ad, opt => opt.MapFrom(src => src.Ask_Id == null ? null : new Ask { Id = src.Ask_Id }))
I get an unsupported mapping error.
Thanks.
I am not sure how your class object looks like, Pre-assuming that you have classes like below,
class Ask
{
public int Id { get; set; }
}
class DestinationDto
{
public Ask Ad { get; set; }
}
class SourceDto
{
public int? Ask_Id { get; set; }
}
If so, then use below mapper.
Mapper.CreateMap<SourceDto, DestinationDto>()
.ForMember(dest => dest.Ad, opt => opt.MapFrom(src => src.Ask_Id == null ? null : new Ask { Id = src.Ask_Id.Value }));
var source = new SourceDto { Ask_Id = 1}; // try with null
var destination = Mapper.Map<SourceDto, DestinationDto>(source);

Norm.MongoException: Connection timeout trying to get connection from connection pool

I'm using Rob's mvc startesite http://mvcstarter.codeplex.com/ with ASP.Net MVC 2, Ninject2, NoRM (http://github.com/atheken/NoRM) and MongoDB. It works so fast and the developpement is even faster but I'm facing a big problem, I at some points, get connection timeout. I can't figure out what I'm doing wrong.
I already asked a question here : I get this error that I don't understand why, using NoRM and Mongo in my MVC project and here http://groups.google.com/group/norm-mongodb/browse_thread/thread/7882be16f030eb29 but I still in the dark.
Thanks a lot for the help!
EDITED*
Here's my MongoSession object :
public class MongoSession : ISession{
private readonly Mongo _server;
public MongoSession()
{
//this looks for a connection string in your Web.config - you can override this if you want
_server = Mongo.Create("MongoDB");
}
public T Single<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class {
return _server.GetCollection<T>().AsQueryable().Where(expression).SingleOrDefault();
}
public IQueryable<T> All<T>() where T : class {
return _server.GetCollection<T>().AsQueryable();
}
public void Save<T>(IEnumerable<T> items) where T : class {
foreach (T item in items) {
Save(item);
}
}
public void Save<T>(T item) where T : class {
var errors = DataAnnotationsValidationRunner.GetErrors(item);
if (errors.Count() > 0)
{
throw new RulesException(errors);
}
_server.Database.GetCollection<T>().Save(item);
}
public void Delete<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class
{
var items = All<T>().Where(expression);
foreach (T item in items)
{
Delete(item);
}
}
public void Delete<T>(T item) where T : class
{
_server.GetCollection<T>().Delete(item);
}
public void Drop<T>() where T : class
{
_server.Database.DropCollection(typeof(T).Name);
}
public void Dispose() {
_server.Dispose();
}
}
And now my MongoRepositoryBase
public abstract class MongoRepositoryBase<T> : ISession<T> where T : MongoObject
{
protected ISession _session;
protected MongoRepositoryBase(ISession session)
{
_session = session;
}
public T Single(ObjectId id)
{
return _session.All<T>().Where(x => x.Id == id).FirstOrDefault();
}
public T Single(Expression<Func<T, bool>> expression)
{
return _session.Single(expression);
}
public IQueryable<T> All()
{
return _session.All<T>();
}
public void Save(IEnumerable<T> items)
{
foreach (T item in items)
{
Save(item);
}
}
public void Save(T item)
{
_session.Save(item);
}
public void Delete(System.Linq.Expressions.Expression<Func<T, bool>> expression)
{
var items = _session.All<T>().Where(expression);
foreach (T item in items)
{
Delete(item);
}
}
public void DeleteAll()
{
var items = _session.All<T>();
foreach (T item in items)
{
Delete(item);
}
}
public void Delete(T item)
{
_session.Delete(item);
}
public void Drop()
{
_session.Drop<T>();
}
public void Dispose()
{
_session.Dispose();
}
}
And an exemple of an other Repository implemantation :
public class PlaceRepository : MongoRepositoryBase<Place>, IPlaceRepository
{
public PlaceRepository(ISession session) : base(session)
{
}
public List<Place> GetByCategory(PlaceCategory category, bool publishedOnly)
{
var query = _session.All<Place>()
.OrderBy(x => x.Name)
.Where(x => x.Category == category);
if (publishedOnly) query = query.Where(x => x.Published);
if (publishedOnly) query = query.Where(x => x.ShowOnMap);
return query.ToList();
}
public Place FindByName(string name)
{
var query = _session.All<Place>()
.Where(x => x.Name.ToLower().Contains(name.ToLower()))
.Where(x => x.Published);
return query.FirstOrDefault();
}
public string[] FindSuggestionsByName(string name)
{
var query = _session.All<Place>()
.OrderBy(x => x.Name)
.Where(x => x.Name.ToLower().StartsWith(name.ToLower()))
.Where(x => x.Published);
var places = query.ToList();
var names = new string[places.Count];
var i = 0;
foreach (var place in places)
{
names[i++] = place.Name;
}
return names;
}
}
Vinny,
I've never used Ninject, so I could be way off with this suggestion. But it seems possible that having a static MongoSession instance might be holding connections open. Have you tried TransientBehavior instead of SingletonBehavior? Or maybe change your code to call Dispose (or use using) after you convert your ShortcutLinks to a List? All
var shortcutLionks = _session.All<ShortcutLinks>().ToList();
_session.Dispose();
A better approach might be to use some sort of repository or DAO where the session details are hidden from the controller. I have a RepositoryBase sample at http://www.codevoyeur.com/Articles/20/A-NoRM-MongoDB-Repository-Base-Class.aspx.
Stuart Harris has a similar, arguably more complete implementation at http://red-badger.com/Blog/post/A-simple-IRepository3cT3e-implementation-for-MongoDB-and-NoRM.aspx
Pooled MongoDB connections are relatively cheap to create, so it's probably best to make sure the data access methods are disposing after your done getting/saving data.
If I add throw new NotImplementedException(); in the Dispose() method of my MongoRepositoryBase class it does not get call so I guess Ninject does not handle this for me, If I had
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
_recipeRepo.Dispose();
base.OnActionExecuted(filterContext);
}
In my controller it does get call. It seems to be fine, thx!

Resources