Nested resolution - dependency-injection

Say I have:
ProductA
ProductB
ProductScreen
ProductAScreen1 : ProductScreen
ProductAScreen2 : ProductScreen
ProductBScreen1 : ProductScreen
ProductBScreen2 : ProductScreen
How can I set it up so that I register the screens locally to the product? So when I am in ProductA and passing IEnumerable it doesn't resolve ProductB's screens?
I thought this would be achievable using something like the lifetime scope, but it doesn't appear I understood it correctly.

Lifetime scope is used to control instance lifetime. You are talking about controlling selection. For that, you should look at the metadata features in Autofac.
Using metadata, you can "tag" each product screen, indicating which product it belongs to:
builder.Register(c => new ProductAScreen1()).As<ProductScreen>()
.WithMetadata<IProductScreenMetadata>(m =>
m.For(am => am.ProductType, typeof(ProductA)));
builder.Register(c => new ProductAScreen2()).As<ProductScreen>()
.WithMetadata<IProductScreenMetadata>(m =>
m.For(am => am.ProductType, typeof(ProductA)));
builder.Register(c => new ProductBScreen1()).As<ProductScreen>()
.WithMetadata<IProductScreenMetadata>(m =>
m.For(am => am.ProductType, typeof(ProductB)));
builder.Register(c => new ProductBScreen2()).As<ProductScreen>()
.WithMetadata<IProductScreenMetadata>(m =>
m.For(am => am.ProductType, typeof(ProductB)));
Next, you can take a dependency on a IEnumerable<Lazy<ProductScreen, IProductScreenMetadata>> and resolve screens according to product type:
var productScreens = _screens.WHere(a => a.Metadata.ProductType == typeof(ProductA));
Update: for completeness, here is a simpler solution using the Keyed approach. First, registration is much simpler:
builder.RegisterType<ProductAScreen1>().Keyed<ProductScreen>(typeof(ProductA));
builder.RegisterType<ProductAScreen2>().Keyed<ProductScreen>(typeof(ProductA));
builder.RegisterType<ProductBScreen1>().Keyed<ProductScreen>(typeof(ProductB));
builder.RegisterType<ProductBScreen2>().Keyed<ProductScreen>(typeof(ProductB));
To resolve a collection of keyed services we'll have to take a dependency on the IIndex<,> type:
public class SomeService
{
private IEnumerable<ProductScreen>> _screens;
public SomeService(IIndex<Type, IEnumerable<ProductScreen>> screens)
{
_screens = screens;
}
public void DoSomething()
{
var screensForProductA = _screens[typeof(ProductA)];
}
}
Note: for the curious: instead of hardcoding the type registrations, here's how you can do a "by convention" registration:
var assembly = ...;
var productTypes = ...; // a collection of all the product types
foreach(var productType in productTypes)
{
builder.RegisterAssemblyTypes(assembly)
.Where(t => typeof(ProductScreen).IsAssignableFrom(t))
.Where(t => t.Name.StartsWith(productType.Name))
.Keyed<ProductScreen>(productType);
}

Related

EF6 nested collection count (without fetching) with/without automapper

According to this documentation page there is a neat way to count nested collection elements without actually fetching them. Great. But the example is for a single item.
Of course, I could iterate and do this in post-processing. But that's not neat.
And here could something like Automapper come into play. It has a ProjectTo extension for IQueryable which can be parameterized (in quite a weird way imho).
Well, I can't get it working. Let's say I have a Client entity that has associated Device entities, and I want to set the DeviceCount property of the target.
By combining the examples from the two sources, something like this could work:
_configuration = new MapperConfiguration(cfg =>
{
SMPContext ctx = null; ///why not an extra property in conf?
cfg.CreateProjection<Client, Models.ViewModels.Client>()
.ForMember(d => d.DeviceCount, conf => conf.MapFrom(s => ctx.Entry(s).Collection(c => c.Devices).Query().Count()));
});
}
...
public async Task<IActionResult> Index()
{
var res = await
_context
.Clients.ProjectTo<SMP.Models.ViewModels.Client>(_configuration, new { ctx = _context })
.ToListAsync();
...
I was experimenting with this too:
public async Task<IActionResult> Index()
{
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateProjection<Client, Models.ViewModels.Client>()
.ForMember(d => d.DeviceCount, conf => conf.MapFrom(s => _context.Entry(s).Collection(c => c.Devices).Query().Count()));
});
var res = await
_context
.Clients.ProjectTo<SMP.Models.ViewModels.Client>(configuration)
.ToListAsync();
...
Either way, I get such an exception, which is quite understandable:
InvalidOperationException: The LINQ expression
'___context_0.Entry(NavigationTreeExpression Value:
EntityReference: Client Expression: c).Collection(c =>
c.Devices).Query() .Count()' could not be translated.
I have tried using Map instead of ProjectTo, but no luck either, as IMapper.Map().AfterMap passes the entire enumerable as parameter instead of per item.
Is there any neat solution?
It looks like there is a simple and neat solution with AutoMapper after all: CreateProjection is smart enough to create a performant SQL mapping in such a case:
builder.Services.AddAutoMapper(config =>
config.CreateProjection<SMP.Models.DataModels.Client, SMP.Models.ViewModels.Client>()
.ForMember(d => d.DeviceCount, o=> o.MapFrom(s => s.Devices.Count))
);
Using it is simple as well:
var res = await _context.Clients
.ProjectTo<Models.ViewModels.Client>(_mapper/*injected IMapper*/.ConfigurationProvider).ToListAsync();
The generated SQL query looks like this:

How to DI repository into Type-class?

Need some help here please ...
I'm looking at the example "graphql-dotnet/server" where the exposed objects contains just plain properties. But what if I need to resolve a property and get data from a repository, how can I get hold of the respository class in the Type-class?
Example:
The sample has a ChatQuery exposing "messages".
public ChatQuery(IChat chat)
{
Field<ListGraphType<MessageType>>("messages", resolve: context => chat.AllMessages.Take(100));
}
Instance "chat" is the repository here and is serving data (messages) via chat.AllMessages.
Let's say that a message has a list of viewers. Then I need to resolve that list from a repository. This is done in the other example "graphql-dotnet/examples" where the "StarWars/Types/StarWarsCharacter.cs" has a list of friends and the "StarWars/Types/HumanType" has the repository (StarWarsData) inserted in the constructor and can be used in the resolve method for "friends":
public class HumanType : ObjectGraphType<Human>
{
public HumanType(StarWarsData data)
{
Name = "Human";
Field(h => h.Id).Description("The id of the human.");
Field(h => h.Name, nullable: true).Description("The name of the human.");
Field<ListGraphType<CharacterInterface>>(
"friends",
resolve: context => data.GetFriends(context.Source)
);
Field<ListGraphType<EpisodeEnum>>("appearsIn", "Which movie they appear in.");
Field(h => h.HomePlanet, nullable: true).Description("The home planet of the human.");
Interface<CharacterInterface>();
}
}
BUT, doing the same thing in the server sample won't work.
public class MessageType : ObjectGraphType<Message>
{
public MessageType(IChat chat)
{
Field(o => o.Content);
Field(o => o.SentAt);
Field(o => o.From, false, typeof(MessageFromType)).Resolve(ResolveFrom);
Field<ListGraphType<Viewer>>(
"viewers",
resolve: context => chat.GetViewers(context.Source)
);
}
private MessageFrom ResolveFrom(ResolveFieldContext<Message> context)
{
var message = context.Source;
return message.From;
}
}
When I add the chat repository to the constructor in MessageType it fails.
I'm obviously missing something here; why isn't Dependency Injection injecting the chat instance into the MessageType class in the "graphql-dotnet/server" project?
But it works in the "graphql-dotnet/examples" project.
Best,
Magnus
To use DI you need to pass a dependency resolver in the constructor of your Schema class. The default resolver uses Activator.CreateInstance, so you have to teach it about the Container you are using.
services.AddSingleton<IDependencyResolver>(
s => new FuncDependencyResolver(s.GetRequiredService));
IDependecyResolver is an interface in the graphql-dotnet project.
public class StarWarsSchema : Schema
{
public StarWarsSchema(IDependencyResolver resolver)
: base(resolver)
{
Query = resolver.Resolve<StarWarsQuery>();
Mutation = resolver.Resolve<StarWarsMutation>();
}
}
https://github.com/graphql-dotnet/examples/blob/bcf46c5c502f8ce75022c50b9b23792e5146f6d2/src/AspNetCore/Example/Startup.cs#L20
https://github.com/graphql-dotnet/examples/blob/bcf46c5c502f8ce75022c50b9b23792e5146f6d2/src/StarWars/StarWarsSchema.cs#L6-L14

How do you properly delete Dependent entites when updating the Principal entity?

I'm having an issue with EFCore 2.0 where its impossible to properly delete dependent entities when updating the prinicipal entity. Here's my code:
Mapping
public class BlueprintMap : IEntityTypeConfiguration<Blueprint>
{
public void Configure(EntityTypeBuilder<Blueprint> builder)
{
builder.ToTable("blueprint").HasKey(t => t.Id);
builder.Property(x => x.Id).HasColumnName("blueprint_id").ValueGeneratedOnAdd();
builder.HasMany<Objective>().WithOne(x => x.Blueprint).OnDelete(DeleteBehavior.Cascade);
}
}
public class ObjectiveMap : IEntityTypeConfiguration<Objective>
{
public void Configure(EntityTypeBuilder<Objective> builder)
{
builder.ToTable("objective").HasKey(x => x.Id);
builder.Property(x => x.Id).HasColumnName("objective_id").ValueGeneratedOnAdd();
builder.HasOne(x => x.Blueprint).WithMany(y => y.Objectives).HasForeignKey("blueprint_id");
}
}
Update WebApi Call
Get from context
var blueprint = await blueprintContext.Blueprints
.Where(x => id == x.Id)
.Include(x => x.Objectives)
.SingleOrDefaultAsync();
Remove objectives from blueprint entity (Objectives is ICollection, the dependent entity
blueprint.Objectives.Remove(objective);
// Get a new context
var blueprintContext = await _blueprintContextFactory.CreateContext();
Blueprint entity AND Blueprint from context have 0 objectives
int x = await blueprintContext.SaveChangesAsync();
Get WebApi Call
Get from context
return await blueprintContext.Blueprints
.Where(x => id == x.Id)
.Include(x => x.Objectives)
.SingleOrDefaultAsync();
blueprint has 2 objectives again.
Stupid mistake. The first context tracks the objectives being removed. When a new context is generated to save the changes, it doesn't know about the objectives being removed so they remain and regenerate. In other words, the Update request is using multiple contexts instead of just 1 per request and this is unacceptable by EF Core's design.

Pass a result of multiple grouping to View

Db.Deliveries.Where(d => d.CrewId != null).GroupBy(d => new {d.Crew, d.Date}).OrderBy(d => d.Key).ToList()
How to pass the result of this code to View? Code is valid. What type must have View?
var Deliveries = Db.Deliveries.Where(d => d.CrewId != null).GroupBy(d => new {d.Crew, d.Date}).OrderBy(d => d.Key).ToList(); //It`s Ok.
List<IGrouping<{Crew:Crew, Date:DateTime}, Delivery>> Deliveries = Db.Deliveries.Where(d => d.CrewId != null).GroupBy(d => new {d.Crew, d.Date}).OrderBy(d => d.Key).ToList(); //It`s wrong.
List<IGrouping<{Crew, DateTime}, Delivery>> //This type is wrong too.
Or is exists any solution of this problem, without using anonymious types?
Sorry for my bad English, Thanks for any help!
Because you use an anonymous type in your GroupBy the result the type exists only virtually.
Either use
#model dynamic
or define a type for your result
public class GroupedDelivery{
public Crew Crew {get;set;}
public DateTime Date {get;set;}
public List<Delivery> Deliveries {get;set;}
}
Map the type in a Select after the GroupBy.
List<GroupedDelivery> Deliveries = Db.Deliveries.Where(d => d.CrewId != null)
.GroupBy(d => new {d.Crew, d.Date})
.Select(p => new GroupedDelivery{
Crew = p.Key.Crew,
Date = p.Key.Date,
Deliveries = p.Select(x => x).ToList()
})
.OrderBy(d => d.Crew).ThenBy(p => p.Date).ToList();

Setting properties on a mocked HttpContextBase

I'm working on an ASP.NET MVC application, and am trying to write some unit tests against controller actions, some of which manipulate properties on the HttpContext, such as Session, Request.Cookies, Response.Cookies, etc. I'm having some trouble figuring out how to "Arrange, Act, Assert"...I can see Arrange and Assert...but I'm having trouble figuring out how to "Act" on properties of a mocked HttpContextBase when all of its properties only have getters. I can't set anything on my mocked context from within my controller actions...so it doesn't seem very useful. I'm fairly new to mocking, so I'm sure there's something that I'm missing, but it seems logical to me that I should be able to create a mock object that I can use in the context of testing controller actions where I can actually set property values, and then later Assert that they're still what I set them to, or something like that. What am I missing?
public static HttpContextBase GetMockHttpContext()
{
var requestCookies = new Mock<HttpCookieCollection>();
var request = new Mock<HttpRequestBase>();
request.Setup(r => r.Cookies).Returns(requestCookies.Object);
request.Setup(r => r.Url).Returns(new Uri("http://example.org"));
var responseCookies = new Mock<HttpCookieCollection>();
var response = new Mock<HttpResponseBase>();
response.Setup(r => r.Cookies).Returns(responseCookies.Object);
var context = new Mock<HttpContextBase>();
context.Setup(ctx => ctx.Request).Returns(request.Object);
context.Setup(ctx => ctx.Response).Returns(response.Object);
context.Setup(ctx => ctx.Session).Returns(new Mock<HttpSessionStateBase>().Object);
context.Setup(ctx => ctx.Server).Returns(new Mock<HttpServerUtilityBase>().Object);
context.Setup(ctx => ctx.User).Returns(GetMockMembershipUser());
context.Setup(ctx => ctx.User.Identity).Returns(context.Object.User.Identity);
context.Setup(ctx => ctx.Response.Output).Returns(new StringWriter());
return context.Object;
}
Not sure if anyone is interested but I have translated the Moq FakeHttpContext to one using Rhino Mocks (my weapon of choice).
public static HttpContextBase FakeHttpContext()
{
var httpContext = MockRepository.GenerateMock<HttpContextBase>();
var request = MockRepository.GenerateMock<HttpRequestBase>();
var response = MockRepository.GenerateMock<HttpResponseBase>();
var session = MockRepository.GenerateMock<HttpSessionStateBase>();
var server = MockRepository.GenerateMock<HttpServerUtilityBase>();
var cookies = new HttpCookieCollection();
response.Stub(r => r.Cookies).Return(cookies);
request.Stub(r => r.Cookies).Return(cookies);
request.Stub(r => r.Url).Return(new Uri("http://test.com"));
httpContext.Stub(x => x.Server).Return(server);
httpContext.Stub(x => x.Session).Return(session);
httpContext.Stub(x => x.Request).Return(request);
httpContext.Stub(x => x.Response).Return(response);
var writer = new StringWriter();
var wr = new SimpleWorkerRequest("", "", "", "", writer);
System.Web.HttpContext.Current = new System.Web.HttpContext(wr);
return httpContext;
}
with a usage in a Unit Test of
_httpContext = FakeHttpContext();
var cookieManager = new CookieManager(_httpContext);
string username = cookieManager.GetUsername();
_httpContext.AssertWasCalled(hc => { var dummy = hc.Request; });
Hey, I think you're just experiencing a bit of a disconnect here, no big deal. What you describe is 100% possible.
I'm not entirely positive on why you can't set properties on your Mocks, but if you post the full code for your test I'd be happy to go through it with you. Just off the top of my head, I'll suggest two things:
There is a difference between Setup() and SetupProperty(). SetupProperty() is probably what you're after if you want to track values on properties, rather than just get a value from them once.
Alternately try calling SetupAllProperties() on any mock that you need to set a property on.
Check the Moq quickstart as well for some examples.

Resources