MVC ASP.net Creating a generic helper - c#

I have two view models, PublishedSong and RadioStation where I want them to have IncrementViews(int id) function.
Instead of copying and pasting the function in to both controllers I wanted to make a generic helper class.
Helper:
public class CustomDBHelper<T>
{
public static IEnumerable<T> GetElements(ApplicationDbContext db, T type)
{
if (type.Equals(typeof(SongsController)))
return (IEnumerable<T>)db.PublishedSongs;
else if (type.Equals(typeof(RadioStationsController)))
return (IEnumerable<T>)db.RadioStations;
else
throw new Exception("Controller not found, DBHelper");
}
}
Controller:
public class MusicBaseController : Controller
{
public JsonResult IncrementViews(int id)
{
using (ApplicationDbContext db = new ApplicationDbContext())
{
db.PublishedSongs.Single(x => x.Id == id);
var element = CustomDBHelper<T>.GetElements(db, this.GetType()).Single(x => x.Id == id);
element.UniquePlayCounts++;
db.SaveChanges();
return Json(new { UniquePlayCounts = element.UniquePlayCounts }, JsonRequestBehavior.AllowGet);
}
}
}
I am having trouble with this line: var element = CustomDBHelper<T>.GetElements(db, this.GetType()).Single(x => x.Id == id);
The <T> is invalid. I am new to generics, I think it expects a class but because the class could either be PublishedSong or RadioStation I don't know how to do it.

The following implementation will work for you:
public class CustomDBHelper
{
public static IEnumerable GetElements(ApplicationDbContext db, Type type)
{
if (type.Equals(typeof(SongsController)))
{
return db.PublishedSongs;
}
else if (type.Equals(typeof(RadioStationsController)))
{
return db.RadioStations;
}
else
{
throw new Exception("Controller not found, DBHelper");
}
}
}
And you will need to cast the result of this function to the required type.
On a side note, type generics are used, well, for being type generic. Since you pass the type as a parameter it's not necessary.

Related

Unit Testing for Web API, GET same ID

I'm at a lost for how to write a unit test for my web api GET by id method.
Here is what I have:
public void GetProduct_ShouldReturnSameID()
{
var context = new TestModelContext();
context.Products.Add(GetDemoProduct());
var controller = new ProductsController(context);
var result = controller.GetProduct(3) as OkNegotiatedContentResult<Product>;
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Content.Id);
}
And my controller method I'm trying to test
public IHttpActionResult GetProduct(int id)
{
var product = (from t in db.Products.Include(t => t.Reviews)
.Where(t => t.Id == id)
select t);
if (product == null || product.Count() == 0)
{
return NotFound();
}
return Ok(product);
}
My test works find with my other controllers, but just not this one. I was wondering what is wrong with this? My test fails with an
"Expected: not null But was: null"
public class TestModelContext : IModelContext {
public TestModelContext() {
this.Products = new TestProductDbSet();
}
public DbSet<Product> Products { get; set; }
public int SaveChanges() {
return 0;
}
public void MarkAsModified(Product item) { }
public void Dispose() { }
}
public class TestProductDbSet : TestDbSet<Product> {
public override Product Find(params object[] keyValues) {
return this.SingleOrDefault(product => product.Id == (int)keyValues.Single());
}
}
public class TestDbSet<T> : DbSet<T>, IQueryable, IEnumerable<T>
where T : class
{
ObservableCollection<T> _data;
IQueryable _query;
public TestDbSet()
{
_data = new ObservableCollection<T>();
_query = _data.AsQueryable();
}
// ...
public override T Create()
{
return Activator.CreateInstance<T>();
}
public override TDerivedEntity Create<TDerivedEntity>()
{
return Activator.CreateInstance<TDerivedEntity>();
}
public override ObservableCollection<T> Local
{
get { return new ObservableCollection<T>(_data); }
}
Type IQueryable.ElementType
{
get { return _query.ElementType; }
}
System.Linq.Expressions.Expression IQueryable.Expression
{
get { return _query.Expression; }
}
IQueryProvider IQueryable.Provider
{
get { return _query.Provider; }
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return _data.GetEnumerator();
}
}
GetDemoProduct:
Product GetDemoProduct()
{
return new Product() { Id = 3, Name = "Name", Reviews = null };
}
According to the sample code there is no code path that would result in the method under test returning null.
It's either going to return a NotFoundResult or a OkNegotiatedContentResult<Product>
Given that it is possible for the method under test to return a NotFoundResult , if the if (product == null || product.Count() == 0) condition is met and the method does indeed return not found result, then the following in your test
...as OkNegotiatedContentResult<Product>;
trying to cast NotFoundResult as OkNegotiatedContentResult<Product> will cause the result to be null.
You should recheck your setup/configuration of your TestModelContext as your linq call is causing the product variable to be null which in turn causes the NotFoundResult to be returned.
UPDATE:
Ok was able to start testing it based on your updated details and found an issue i should have noticed earlier.
First I was getting an error when added fake Product to list and had to update the TestDbSet base class to include added entities. I'll assume it was omitted in the sample code.
public override T Add(T entity) {
_data.Add(entity);
return entity;
}
next in the method under test, given the name the method and the expectation in the test, it should be returning a single Product. You were returning the query which would also result in null when you did the cast in the test.
public IHttpActionResult GetProduct(int id) {
var product = (from t in db.Products.Include(t => t.Reviews)
.Where(t => t.Id == id)
select t);
if (product == null || product.Count() == 0) {
return NotFound();
}
return Ok(product.First());
}
When the above two changes were made, the test passed as expected.
It looks like the controller is returning a null as the result. Most likely the Products table and/or the Reviews table does not include a value of 3 that you are passing in as the parameter to your web method.
This is what I suspect. Your GetDemoProduct() method which returns the product you are adding does not have Reviews. Hence, your query:
var product = (from t in db.Products.Include(t => t.Reviews)
.Where(t => t.Id == id)
select t);
did not return any records. Just a thought. If you can show us your GetDemoProduct() then we can tell.

Moq - setup Where extension?

How to set up where linq extension on object? DbSet in my case. Here is my code:
this.workflowStateSet
.Setup(m => m.Where(It.IsAny<Expression<Func<Model.WorkflowState, int, bool>>>()))
.Returns(new List<Model.WorkflowState>().AsQueryable());
However, it gives me exception not very familiar exception:
System.NotSupportedException: Expression references a method that
does not belong to the mocked object: m => m.Where<WorkflowState>
I will be grateful for any hint.
This extension method will help mock the DbSet
public static class MockDbSetExtensions {
public static Mock<DbSet<T>> AsDbSetMock<T>(this IEnumerable<T> list) where T : class {
IQueryable<T> queryableList = list.AsQueryable();
Mock<DbSet<T>> dbSetMock = new Mock<DbSet<T>>();
dbSetMock.As<IQueryable<T>>().Setup(x => x.Provider).Returns(queryableList.Provider);
dbSetMock.As<IQueryable<T>>().Setup(x => x.Expression).Returns(queryableList.Expression);
dbSetMock.As<IQueryable<T>>().Setup(x => x.ElementType).Returns(queryableList.ElementType);
dbSetMock.As<IQueryable<T>>().Setup(x => x.GetEnumerator()).Returns(() => queryableList.GetEnumerator());
return dbSetMock;
}
}
And you can use it like this.
//Arrange
var data = new List<Model.WorkflowState>();
//you would populate your list as needed.
//convert it to a mock DbSet that uses the list as its datasource
var workflowStateSet = data.AsDbSetMock();
var dbSet = workflowStateSet.Object;
//Act
var items = dbSet.Where("Your expression here");
//Assert
//....
Use the repository pattern to add a layer of abstraction to the data retrieval. This abstraction can then be mocked.
If, for example, you were trying to retrieve all of the workflows with a stateId equal to 1, then rather than calling something like this
var result = DbSet.WorkflowState.Where(w => w.stateId == 1);
move this code into another class and then create an interface of the method signature.
public interface IWorkflowStateSetRepository{
IQueryable<Model.WorkflowState> GetAllWorkflows(int state);
}
implementation
public class WorkflowStateSetRepository : IWorkflowStateSetRepository{
public IQueryable<Model.WorkflowState> GetAllWorkflows(int state){
return DbSet.WorkflowState .Where(w => w.stateId == state);
}
}
In the calling code get an instance of IWorkflowStateSetRepository (probably from your IoC container) and call the GetAllWorkflows() method instead. This will give you the same results as before but you can now mock the interface in your tests and setup calls to that methods.
this.MockedIWorkflowStateSetRepository.Setup(m => m.GetAllWorkflows(It.IsAny<int>()))
.Returns(new List<Model.WorkflowState>().AsQueryable());
This code is more maintainable and (with appropriately named variables and methods) also conveys the intent a lot better.
The repository pattern is discussed in greater detail here;
http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
Are you trying to Mock up a real DbSet instance ? Cause this won't work, as the error message try to explain you. To mock a type, it must either be an interface or have virtual members (abstract members are also virtual).
You can try to mock up IDbSet or to create a custom DbSet class, for instance something like the following class
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
public class DbSetMock<T> : DbSet<T>, IDbSet<T>
where T : class
{
private readonly ICollection<T> _contentCollection;
public DbSetMock(IList<T> contentCollection = null)
{
_contentCollection = new Collection<T>(contentCollection ?? new List<T>());
AddedEntities = new List<T>();
RemovedEntities = new List<T>();
AttachedEntities = new List<T>();
}
public void OverrideContentCollection(IEnumerable<T> newData)
{
_contentCollection.Clear();
_contentCollection.AddRange(newData);
}
public IList<T> AddedEntities { get; private set; }
public IList<T> AttachedEntities { get; private set; }
public override ObservableCollection<T> Local
{
get
{
throw new NotImplementedException();
}
}
public IList<T> RemovedEntities { get; private set; }
public Type ElementType
{
get
{
return typeof(T);
}
}
public Expression Expression
{
get
{
return _contentCollection.AsQueryable().Expression;
}
}
public IQueryProvider Provider
{
get
{
return _contentCollection.AsQueryable().Provider;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<T> GetEnumerator()
{
return _contentCollection.GetEnumerator();
}
public override T Add(T entity)
{
AddedEntities.Add(entity);
_contentCollection.Add(entity);
return entity;
}
public override T Attach(T entity)
{
AttachedEntities.Add(entity);
var matchingEntity = _contentCollection.SingleOrDefault(x => x.Id == entity.Id);
if (matchingEntity != null)
{
_contentCollection.Remove(matchingEntity);
}
_contentCollection.Add(entity);
return entity;
}
public override TDerivedEntity Create<TDerivedEntity>()
{
throw new NotImplementedException();
}
public override T Create()
{
throw new NotImplementedException();
}
public override T Find(params object[] keyValues)
{
throw new NotImplementedException();
}
public override T Remove(T entity)
{
RemovedEntities.Add(entity);
_contentCollection.Remove(entity);
return entity;
}
}
You can use constructor parameter to setup content that will be retrieved by the db set.
Hope this helps.

How to access an unknown property in C# Generics

I am creating a general repository class as part of my Entity Framework Code First data layer. To get the single row by id, if all the entities have id name as "ID" it will work as shown by the following:
public T GetSingle(int id)
{
return GetAll().FirstOrDefault(x => x.ID == id);
}
But I would like to name my entity primarykey as "EnityName"+Id, such as AddressId or ApplicantId etc. Is there any way to have the code:
return GetAll().FirstOrDefault(x => x.<EntityName>Id == id);
to make to work?
Thanks
You can do it easier only create method "Get single" and pass predicate
public virtual T GetSingle(Expression<Func<T, bool>> predicate)
{
if (predicate != null)
{
return context.Set<T>().Where(predicate).SingleOrDefault();
}
else
{
throw new ArgumentNullException("Predicate value is null");
}
}
And call it:
yourContext.GetSingle(g => g.Id == id);
you can use Attributes for this and read the Property which has the right Attribute.
public abstract class MyIdAttribute: Attribute
{
}
public class MyClass
{
[MyId]
WhatEverName {get; set;}
}
and in your Method
public T GetSingle(int id)
{
var propertyInfos = typeof(T).GetProperties().Where(property => property.GetCustomAttributes<MyIdAttribute>().Count() > 0).ToList();
if (propertyInfos.Any())
{
foreach(T current in GetAll())
{
object value = propertyInfos[0].GetValue(current);
if(value == id)
{
return current;
}
}
}
}
Cheers
Thomas

MongoDB C# GetById using Find

public abstract class GenericRepository<T> : IDisposable, IGenericRepository<T> where T : class
{
protected SphereTripMongoDbContext SphereTripMongoDbContext;
public IMongoCollection<T> MongoCollection { get; set; }
protected GenericRepository(SphereTripMongoDbContext sphereTripMongoDbContext)
{
SphereTripMongoDbContext = sphereTripMongoDbContext;
MongoCollection =
SphereTripMongoDbContext.MongoDatabase.GetCollection<T>(typeof(T).Name);
}
public void Dispose()
{
throw new NotImplementedException();
}
public T GetById(string id)
{
var entity = MongoCollection**.Find(t => t.Id == id)**;
return entity;
}
}
I am trying write a generic abstract repository class for MongoDb. Since I am using Generic type in the base class, the "Id" is not visible when I am finding the document using Find method. Not sure how to fix the issue.
Any help would be appreciated.
You can use Find without using a typed lambda expression with Builders:
var item = await collection
.Find(Builders<ItemClass>.Filter.Eq("_id", id))
.FirstOrDefaultAsync();
However, a more robust solution would be to use some interface that gives you what you need (i.e. ID) and making sure GenericRepository only works with these types:
interface IIdentifiable
{
string Id { get; }
}
class GenericRepository <T> : ... where T : IIdentifiable
{
// ...
}
I built a method like this:
public ValueTask<T> GetAsync<T>(IQueryable<T> source, object[] keyValues, CancellationToken cancellationToken = default)
where T : class
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
if (keyValues == default)
{
throw new ArgumentNullException(nameof(keyValues));
}
if (keyValues.Length != 1)
{
throw new ArgumentException("Key values must contain exactly one key value", nameof(keyValues));
}
var type = typeof(T);
var classMap = BsonClassMap.LookupClassMap(type);
if (classMap == default)
{
throw new InvalidOperationException($"Class map not found for '{type.Name}'");
}
var id = classMap.IdMemberMap;
if (id == default)
{
throw new InvalidOperationException($"Id member not found for '{type.Name}'");
}
var filter = Builders<T>.Filter.Eq(id.ElementName, keyValues[0]);
var collection = Database.GetCollection<T>(type.Name);
async ValueTask<T> GetAsync()
{
var cursor = await collection.FindAsync<T>(filter, default, cancellationToken).ConfigureAwait(false);
return await cursor.SingleOrDefaultAsync(cancellationToken).ConfigureAwait(false);
}
return GetAsync();
}

How to modify a method to be able to return multiple types instead of just `object`?

I have this method, and I'm convinced that it's the source of another issue I'm having. I believe it's because this method returns a type of object instead of one of the three concrete types it actually returns.
Here's the method:
public object GetData(TableType type)
{
switch (type)
{
case TableType.Person:
return new Domain.PersonList(_personRepository.Get());
case TableType.Event:
return new Domain.EventList(_eventRepository.Get());
case TableType.User:
return new Domain.UserList(_userRepository.Get());
}
return null;
}
How can I modify this method to return a type other than object?
What you need is generics.
This way the method will return the type according to type.
This method for example returns TService type:
private TService GetService<TService>(ServiceInfo serviceInfo)
where TService : class, ICoordinationExecutionService
{
var service = _executionServices.FirstOrDefault(
x => x.ServiceInfo.InstanceInfo.InstanceId == serviceInfo.InstanceInfo.InstanceId
&& serviceInfo.ServiceTypeName.Equals(x.ServiceInfo.ServiceTypeName));
if (service == null)
{
throw new Exception("invalid service ");
}
return _directory.GetService<TService>(service.ServiceInfo);
}
Another possibility is to use Interfaces to guarantee that only one of your three types is returned.
public IDomainList GetData(TableType type)
{
switch (type)
{
case TableType.Person:
return new Domain.PersonList(_personRepository.Get());
case TableType.Event:
return new Domain.EventList(_eventRepository.Get());
case TableType.User:
return new Domain.UserList(_userRepository.Get());
}
return null;
}
So long as PersonList, EventList, and UserList all implement the IDomainList interface, then you are guaranteed to return on of those three types. Then in your implementation, you can determine what to do based on the specific type returned.
First, you must identify what is the "lowest common denominator" base class for the three kinds of repositories. If you do not have one, then you should create one. One way to do this would be
public abstract class repositoryBase
{
public virtual IList<repositoryBase> Get() { }
}
Then each of the three classes woudl inherit from repositoryBase:
public personRepository : repositoryBase
{
public override IList<personRepository> Get()
{
// code here to return the list
}
}
Once you have refactored the class hierarchy in this way, then you don't even need the GetData method. Where you were calling GetData, you now just call someRepository.Get();
If you are already inheriting from something else and adding a Get method to the base class is not a good fit, you can do the same thing I describe here using an Interface. Either approach, base class or interface work equally well in this case and both are good OO practice.
You could use generics to get this done,
public T GetData<T>()
{
if(typeof(T) == typeof(Domain.PersonList))
return new Domain.PersonList(_personRepository.Get());
else if (typeof(T) == typeof(Domain.EventList))
return new Domain.EventList(_eventRepository.Get());
else if (typeof(T) == typeof(Domain.UserList))
return new Domain.UserList(_userRepository.Get());
}
return default(T);
}
You can create three GetData functions, that will get the type as a parameter and return the correct type.
public List<TableType.Person> GetData(TableType.Person type)
{
return new Domain.PersonList(_personRepository.Get());
}
public List<TableType.Event> GetData(TableType.Event type)
{
return new Domain.EventList(_eventRepository.Get());
}
public List<TableType.User> GetData(TableType.User type)
{
return new Domain.UserList(_userRepository.Get());
}
The runtime will pick the correct overload according to the type of the parameter.
EDIT: I am assuming that the Domain.xxxxList functions return a list of the mentioned type in this example. It is not required, of course.
Here's a whole answer with a general solution for parameterized types for return value.
class Program
{
public enum TableType
{
Person,
Event,
User
}
class Person
{
public string Name { get; set; }
public Person(string name) { this.Name = name; }
}
class Event
{
public int EventType { get; set; }
public Event(int eventType) { this.EventType = eventType; }
}
class User
{
public string UserName { get; set; }
public User(string username) { this.UserName = username; }
}
public static T GetData<T>(TableType tableType)
{
switch (tableType)
{
case TableType.Person:
Person person = new Person("John");
return (T)Convert.ChangeType(person, typeof(T));
case TableType.Event:
Event evt = new Event(2);
return (T)Convert.ChangeType(evt, typeof(T));
case TableType.User:
User user = new User("jjesus");
return (T)Convert.ChangeType(user, typeof(T));
}
return default(T);
}
static void Main(string[] args)
{
Person person = GetData<Person>(TableType.Person);
Console.WriteLine("Person.Name {0}", person.Name);
Console.WriteLine("Hit any key to continue");
Console.ReadKey();
}
}

Categories

Resources