Should I use IActionResult with Produces or ActionResult<> for View()? - asp.net-mvc

In my controller I have method that always produces View(). I would like to mark return type for api explorer(swagger). What is right way to do so in asp.net?
public class ProductController {
[Produces(typeof(MyType))]
public IActionResult MethodA() { ... }
public ActionResult<MyType> MethodB() { ... }
}

Yes you can, take a look on my solution :
I explained some very important things to you in the comments, do not forget to read it.
Controller:
// Declare the ProductService Interface
// ProductService contains all of methods you need to get products information
public class ProductController : Controller
{
private readonly ProductService _productService;
// Actions
public ActionResult Index()
{
var res = new SearchResult<Product>(); // *Product* is a Class in Model that contains all the variables that you need (product properties)
return View(res);
}
public ActionResult GetProducts(DataSourceRequest request)
{
var result = _productService.GetProducts(request); // GetProducts is a method in ProductService
return Json(result); // you should build your Json swagger
}
}
Model:
Product.cs :
public string productId { get; set; }
public string productName { get; set; }
public string productPrice { get; set; }
public bool productAvailability { get; set; }
ProductService.cs :
public partial class ProductService : IProductService // IProductService is an interface of IProductService, i will defined it after ProductService
{
// To get list of products :
public List<Product> GetProducts()
{
// Initialize new List of products
List<Product> products= new List<Product>();
// Your Entity hier
EntityContext ctx = Enter_youre_Entity_Hier();
IQueryable<ProductView> query = ctx.ProductViewSet; // At the bottom of the solution you'll find what it is ProductViewSet and how you create one
return products;
}
// Search result
public SearchResult<Product> GetProducts()
{
SearchResult<Product> res;
EntityContext ctx = Enter_youre_Entity_Hier();
IQueryable<ProductView> query =
DataSourceRequest.BuildLinqQueryFromRequest(Ctx.ProductViewSet);
query = query.Select(p => p);
var productSort = query.ToList();
res = new SearchResult<Product>(
productSort.ConvertAll<Product>(p => MapProductViewToProduct(p))); // MapProductViewToProduct to mapping the data between Database table "ProductView" to tour local variables in "Product" model
return res;
}
// Mapping data between ProductView and Product
public Product MapProductViewToProduct(ProductView productView )
{
Product product = new Product ();
product.productId = productView.productId;
product.productName = productView.productName;
product.productPrice = productView.productPrice;
product.productAvailability = productView.productAvailability;
return product;
}
}
IProductService.cs :
You have to defined an interface of ProductService.
public interface IProductService
{
SearchResult<Product> GetProducts();
}
What is ProductView?
First view in SQL is a virtual table based on the result-set of an SQL statement. A view contains rows and columns, just like a real table. The fields in a view are fields from one or more real tables in the database.
CREATE ProductView :
CREATE VIEW ProductView AS
SELECT productId,
productName,
productPrice,
productAvailability
FROM table_name
Then you have to update your Entity Framework to use ProductView.

Related

Is running a query from a command a violation of Command-Query Separation?

Given a real-world anonymous shopping cart, the "AddToCart" workflow must do the following steps:
Lookup the current product from the database. Get the price from the product or use a service to calculate the price on user selections and other product properties. (query)
Lookup the current shopping cart from the database. (query)
If the current shopping cart doesn't exist in the database, create a new shopping cart entity (in memory).
Add the new item (product) to the shopping cart entity (in memory) along with its price.
Run any discount calculations on the entire shopping cart. (depends on query)
Run any sales tax calculations on the shopping cart. (depends on query)
Run any shipping calculations on the shopping cart. (depends on query)
If this is a new shopping cart, add the entity to the database, otherwise update the shopping cart in the database. (command)
So, although "AddToCart" sounds like it should be a command (since it updates the system state), in practice it depends on many queries.
My Question
What is the generally accepted way to handle workflows like this?
Make an AddToCartCommandHandler that depends on other services that may run queries.
Make a facade CartService that orchestrates the workflow that runs the queries first followed by the commands.
Make the controller action method first run the queries, then run any commands. Seems like some of the query steps could be missed if this needs to be reused.
Other?
Is the reason I can't find an answer about this because it "depends on the design" and this is one of the exceptions where not to apply it?
If the commands and queries are separated, would I pass my real entity framework entity class to the command that adds/updates the cart (so EF can work out whether it is attached or not)? It seems like a DTO won't do in this case.
NOTE: I am implicitly assuming that systems that implement CQS do so with the aim that eventually they could become a full-on CQRS system. If so, this workflow apparently would not be able to make the transition - hence my question.
Background
I am taking my first stab at CQS.
It is clear from the documentation I have read about this pattern that a query must not change the system state.
However, it is unclear whether it is considered okay to run a query from within a command (I can't seem to find any info anywhere).
There are several real-world cases I can think of where this needs to happen. But, given the lack of real-world examples of this pattern online I am uncertain how to proceed. There is lots of theory online, but the only code I can find is here and here.
The answer to this problem came in the form of a comment by qujck.
The solution is to break the application into different query types and command types. The exact purpose of each type remain a mystery (since the blog post doesn't go into the reasons why he made this distinction), but it does make it clear how top-level and mid-level commands can depend on database queries.
Command Types
Command (top-level)
Command Strategy (mid-level)
Data Command (direct data access)
Query Types
Query (top-level)
Query Strategy (mid-level)
Data Query (direct data access)
Command-Query Implementation
// Commands
public interface ICommand
{
}
public interface IDataCommand
{
}
/// <summary>
/// A holistic abstraction, an abstraction that acts as the whole of each transaction
/// </summary>
/// <typeparam name="TCommand"></typeparam>
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
public interface ICommandStrategyHandler<TCommand> where TCommand : ICommand
{
void Handle(TCommand command);
}
/// <summary>
/// Direct database update
/// </summary>
/// <typeparam name="TCommand"></typeparam>
public interface IDataCommandHandler<TCommand> where TCommand : IDataCommand
{
void Handle(TCommand command);
}
// Queries
public interface IQuery<TResult>
{
}
public interface IDataQuery<TResult>
{
}
/// <summary>
/// A holistic abstraction, an abstraction that acts as the whole of each transaction
/// </summary>
/// <typeparam name="TQuery"></typeparam>
/// <typeparam name="TResult"></typeparam>
public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
public interface IQueryStrategyHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
/// <summary>
/// Direct database query
/// </summary>
/// <typeparam name="TQuery"></typeparam>
/// <typeparam name="TResult"></typeparam>
public interface IDataQueryHandler<TQuery, TResult> where TQuery : IDataQuery<TResult>
{
TResult Handle(TQuery query);
}
/// <summary>
/// Generic processor that can run any query
/// </summary>
public interface IQueryProcessor
{
TResult Execute<TResult>(IQuery<TResult> query);
// NOTE: Stephen recommends against using Async. He may be right that it is not
// worth the aggrevation of bugs that may be introduced.
//Task<TResult> Execute<TResult>(IQuery<TResult> query);
TResult Execute<TResult>(IDataQuery<TResult> query);
}
AddToCart Dependency Graph
Using the above implementation, the structure of the AddToCart workflow dependency graph looks like this.
AddToCartCommandHandler : ICommandHandler<AddToCartCommand>
GetShoppingCartDetailsQueryHandler : IQueryHandler<GetShoppingCartDetailsQuery, ShoppingCartDetails>
GetShoppingCartQueryStrategyHandler : IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails>
GetShoppingCartDataQueryHandler : IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails>
ApplicationDbContext
CreateShoppingCartDataCommandHandler : IDataCommandHandler<CreateShoppingCartDataCommand>
ApplicationDbContext
UpdateShoppingCartDataCommandHandler : IDataCommandHandler<UpdateShoppingCartDataCommand>
SetItemPriceCommandStrategyHandler : ICommandStrategyHandler<SetItemPriceCommandStrategy>
GetProductDetailsDataQueryHandler : IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails>
ApplicationDbContext
SetTotalsCommandStrategyHandler : ICommandStrategyHandler<SetTotalsCommandStrategy>
SetDiscountsCommandStrategyHandler : ICommandStrategyHandler<SetDiscountsCommandStrategy>
?
SetSalesTaxCommandStrategyHandler : ICommandStrategyHandler<SetSalesTaxCommandStrategy>
Implementation
DTOs
public class ShoppingCartDetails : IOrder
{
private IEnumerable<IOrderItem> items = new List<ShoppingCartItem>();
public Guid Id { get; set; }
public decimal SubtotalDiscounts { get; set; }
public string ShippingPostalCode { get; set; }
public decimal Shipping { get; set; }
public decimal ShippingDiscounts { get; set; }
public decimal SalesTax { get; set; }
public decimal SalesTaxDiscounts { get; set; }
// Declared twice - once for the IOrder interface
// and once so we can get the realized concrete type.
// See: https://stackoverflow.com/questions/15490633/why-cant-i-use-a-compatible-concrete-type-when-implementing-an-interface
public IEnumerable<ShoppingCartItem> Items
{
get { return this.items as IEnumerable<ShoppingCartItem>; }
set { this.items = value; }
}
IEnumerable<IOrderItem> IOrder.Items
{
get { return this.items; }
set { this.items = value; }
}
//public IEnumerable<ShoppingCartNotification> Notifications { get; set; }
//public IEnumerable<ShoppingCartCoupon> Coupons { get; set; } // TODO: Add this to IOrder
}
public class ShoppingCartItem : IOrderItem
{
public ShoppingCartItem()
{
this.Id = Guid.NewGuid();
this.Selections = new Dictionary<string, object>();
}
public Guid Id { get; set; }
public Guid ShoppingCartId { get; set; }
public Guid ProductId { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
public decimal PriceDiscount { get; set; }
public IDictionary<string, object> Selections { get; set; }
}
public class ProductDetails
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public decimal Discount { get; set; }
}
Calculating Order Totals
Rather than relying on a string of services to do simple (and required) arithmetic, I opted to put this behavior into extension methods so it is done on the fly against the actual data. Since this logic will need to be shared between the shopping cart, order, and quote, the calculation is done against IOrder and IOrderItem rather than concrete model types.
// Contract to share simple cacluation and other business logic between shopping cart, order, and quote
public interface IOrder
{
decimal SubtotalDiscounts { get; set; }
decimal Shipping { get; set; }
decimal ShippingDiscounts { get; set; }
decimal SalesTax { get; set; }
decimal SalesTaxDiscounts { get; set; }
IEnumerable<IOrderItem> Items { get; set; }
}
public interface IOrderItem
{
Guid ProductId { get; set; }
int Quantity { get; set; }
decimal Price { get; set; }
decimal PriceDiscount { get; set; }
IDictionary<string, object> Selections { get; set; }
}
public static class OrderExtensions
{
public static decimal GetSubtotal(this IOrder order)
{
return order.Items.Sum(x => x.GetTotal());
}
public static decimal GetSubtotalBeforeDiscounts(this IOrder order)
{
return order.Items.Sum(x => x.GetTotalBeforeDiscounts());
}
public static decimal GetTotal(this IOrder order)
{
var subtotal = (order.GetSubtotal() - order.SubtotalDiscounts);
var shipping = (order.Shipping - order.ShippingDiscounts);
var salesTax = (order.SalesTax - order.SalesTaxDiscounts);
return (subtotal + shipping + salesTax);
}
}
public static class OrderItemExtensions
{
public static decimal GetTotalBeforeDiscounts(this IOrderItem item)
{
return (item.Price * item.Quantity);
}
public static decimal GetTotal(this IOrderItem item)
{
return (GetTotalBeforeDiscounts(item) - item.PriceDiscount);
}
public static decimal GetDiscountedUnitPrice(this IOrderItem item)
{
return (item.Quantity > 0) ? (GetTotal(item) / item.Quantity) : 0;
}
}
ShoppingCartController
For brevity, we only show the AddToCart action, but this is where other actions against the shopping cart (i.e. remove from cart) would go as well.
public class ShoppingCartController : Controller
{
private readonly IQueryProcessor queryProcessor;
private readonly IAnonymousIdAccessor anonymousIdAccessor;
private readonly ICommandHandler<AddToCartCommand> addToCartHandler;
public ShoppingCartController(
IQueryProcessor queryProcessor,
IAnonymousIdAccessor anonymousIdAccessor,
ICommandHandler<AddToCartCommand> addToCartHandler)
{
if (queryProcessor == null)
throw new ArgumentNullException("queryProcessor");
if (anonymousIdAccessor == null)
throw new ArgumentNullException("anonymousIdAccessor");
if (addToCartHandler == null)
throw new ArgumentNullException("addToCartHandler");
this.queryProcessor = queryProcessor;
this.anonymousIdAccessor = anonymousIdAccessor;
this.addToCartHandler = addToCartHandler;
}
public ActionResult Index()
{
var command = new GetShoppingCartDetailsQuery
{
ShoppingCartId = this.anonymousIdAccessor.AnonymousID
};
ShoppingCartDetails cart = this.queryProcessor.Execute(command);
return View(cart);
}
public ActionResult AddToCart(ItemViewModel model)
{
var command = new AddToCartCommand
{
ProductId = model.Id,
Quantity = model.Qty,
Selections = model.Selections,
ShoppingCartId = this.anonymousIdAccessor.AnonymousID
};
this.addToCartHandler.Handle(command);
// If we execute server side, it should go to the cart page
return RedirectToAction("Index");
}
}
AddToCartCommandHandler
Here is where the main part of the workflow is executed. This command will be called directly from the AddToCart controller action.
public class AddToCartCommandHandler : ICommandHandler<AddToCartCommand>
{
private readonly IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails> getShoppingCartQuery;
private readonly IDataCommandHandler<UpdateShoppingCartDataCommand> updateShoppingCartCommand;
private readonly ICommandStrategyHandler<SetItemPriceCommandStrategy> setItemPriceCommand;
private readonly ICommandStrategyHandler<SetTotalsCommandStrategy> setTotalsCommand;
public AddToCartCommandHandler(
IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails> getShoppingCartCommand,
IDataCommandHandler<UpdateShoppingCartDataCommand> updateShoppingCartCommand,
ICommandStrategyHandler<SetItemPriceCommandStrategy> setItemPriceCommand,
ICommandStrategyHandler<SetTotalsCommandStrategy> setTotalsCommand
)
{
if (getShoppingCartCommand == null)
throw new ArgumentNullException("getShoppingCartCommand");
if (setItemPriceCommand == null)
throw new ArgumentNullException("setItemPriceCommand");
if (updateShoppingCartCommand == null)
throw new ArgumentNullException("updateShoppingCartCommand");
if (setTotalsCommand == null)
throw new ArgumentNullException("setTotalsCommand");
this.getShoppingCartQuery = getShoppingCartCommand;
this.updateShoppingCartCommand = updateShoppingCartCommand;
this.setItemPriceCommand = setItemPriceCommand;
this.setTotalsCommand = setTotalsCommand;
}
public void Handle(AddToCartCommand command)
{
// Get the shopping cart (aggregate root) from the database
var shoppingCart = getShoppingCartQuery.Handle(new GetShoppingCartQueryStrategy { ShoppingCartId = command.ShoppingCartId });
// Create a new shopping cart item
var item = new Contract.DTOs.ShoppingCartItem
{
ShoppingCartId = command.ShoppingCartId,
ProductId = command.ProductId,
Quantity = command.Quantity,
// Dictionary representing the option selections the user made on the UI
Selections = command.Selections
};
// Set the item's price (calculated/retrieved from database query)
setItemPriceCommand.Handle(new SetItemPriceCommandStrategy { ShoppingCartItem = item });
// Add the item to the cart
var items = new List<Contract.DTOs.ShoppingCartItem>(shoppingCart.Items);
items.Add(item);
shoppingCart.Items = items;
// Set the shopping cart totals (sales tax, discounts)
setTotalsCommand.Handle(new SetTotalsCommandStrategy { ShoppingCart = shoppingCart });
// Update the shopping cart details in the database
updateShoppingCartCommand.Handle(new UpdateShoppingCartDataCommand { ShoppingCart = shoppingCart });
}
}
GetShoppingCartQueryStrategyHandler
public class GetShoppingCartQueryStrategyHandler : IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails>
{
private readonly IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails> getShoppingCartDataQuery;
private readonly IDataCommandHandler<CreateShoppingCartDataCommand> createShoppingCartDataCommand;
public GetShoppingCartQueryStrategyHandler(
IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails> getShoppingCartDataQuery,
IDataCommandHandler<CreateShoppingCartDataCommand> createShoppingCartDataCommand)
{
if (getShoppingCartDataQuery == null)
throw new ArgumentNullException("getShoppingCartDataQuery");
if (createShoppingCartDataCommand == null)
throw new ArgumentNullException("createShoppingCartDataCommand");
this.getShoppingCartDataQuery = getShoppingCartDataQuery;
this.createShoppingCartDataCommand = createShoppingCartDataCommand;
}
public ShoppingCartDetails Handle(GetShoppingCartQueryStrategy query)
{
var result = this.getShoppingCartDataQuery.Handle(new GetShoppingCartDataQuery { ShoppingCartId = query.ShoppingCartId });
// If there is no shopping cart, create one.
if (result == null)
{
this.createShoppingCartDataCommand.Handle(new CreateShoppingCartDataCommand { ShoppingCartId = query.ShoppingCartId });
result = new ShoppingCartDetails
{
Id = query.ShoppingCartId
};
}
return result;
}
}
GetShoppingCartDataQueryHandler
/// <summary>
/// Data handler to get the shopping cart data (if it exists)
/// </summary>
public class GetShoppingCartDataQueryHandler : IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails>
{
private readonly IAppContext context;
public GetShoppingCartDataQueryHandler(IAppContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public ShoppingCartDetails Handle(GetShoppingCartDataQuery query)
{
return (from shoppingCart in context.ShoppingCarts
where shoppingCart.Id == query.ShoppingCartId
select new ShoppingCartDetails
{
Id = shoppingCart.Id,
SubtotalDiscounts = shoppingCart.SubtotalDiscounts,
ShippingPostalCode = shoppingCart.ShippingPostalCode,
Shipping = shoppingCart.Shipping,
ShippingDiscounts = shoppingCart.ShippingDiscounts,
SalesTax = shoppingCart.SalesTax,
SalesTaxDiscounts = shoppingCart.SalesTaxDiscounts,
Items = shoppingCart.Items.Select(i =>
new Contract.DTOs.ShoppingCartItem
{
Id = i.Id,
ShoppingCartId = i.ShoppingCartId,
ProductId = i.ProductId,
Quantity = i.Quantity,
Price = i.Price,
PriceDiscount = i.PriceDiscount
// TODO: Selections...
})
}).FirstOrDefault();
}
}
CreateShoppingCartDataCommandHandler
public class CreateShoppingCartDataCommandHandler : IDataCommandHandler<CreateShoppingCartDataCommand>
{
private readonly IAppContext context;
public CreateShoppingCartDataCommandHandler(IAppContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public void Handle(CreateShoppingCartDataCommand command)
{
var cart = new ShoppingCart
{
Id = command.ShoppingCartId
};
this.context.ShoppingCarts.Add(cart);
this.context.SaveChanges();
}
}
UpdateShoppingCartDataCommandHandler
This updates the shopping cart with all of the changes that the business layer applied.
For the time being, this "command" does a query so it can reconcile the differences between the database and in memory copy. However, it is obviously a violation of the CQS pattern. I plan to make a follow-up question to determine what the best course of action is for change tracking since change tracking and CQS appear to be intimately linked.
public class UpdateShoppingCartDataCommandHandler : IDataCommandHandler<UpdateShoppingCartDataCommand>
{
private readonly IAppContext context;
public UpdateShoppingCartDataCommandHandler(IAppContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public void Handle(UpdateShoppingCartDataCommand command)
{
var cart = context.ShoppingCarts
.Include(x => x.Items)
.Single(x => x.Id == command.ShoppingCart.Id);
cart.Id = command.ShoppingCart.Id;
cart.SubtotalDiscounts = command.ShoppingCart.SubtotalDiscounts;
cart.ShippingPostalCode = command.ShoppingCart.ShippingPostalCode;
cart.Shipping = command.ShoppingCart.Shipping;
cart.ShippingDiscounts = command.ShoppingCart.ShippingDiscounts;
cart.SalesTax = command.ShoppingCart.SalesTax;
cart.SalesTaxDiscounts = command.ShoppingCart.SalesTaxDiscounts;
ReconcileShoppingCartItems(cart.Items, command.ShoppingCart.Items, command.ShoppingCart.Id);
// Update the cart with new data
context.SaveChanges();
}
private void ReconcileShoppingCartItems(ICollection<ShoppingCartItem> items, IEnumerable<Contract.DTOs.ShoppingCartItem> itemDtos, Guid shoppingCartId)
{
// remove deleted items
var items2 = new List<ShoppingCartItem>(items);
foreach (var item in items2)
{
if (!itemDtos.Any(x => x.Id == item.Id))
{
context.Entry(item).State = EntityState.Deleted;
}
}
// Add/update items
foreach (var dto in itemDtos)
{
var item = items.FirstOrDefault(x => x.Id == dto.Id);
if (item == null)
{
items.Add(new ShoppingCartItem
{
Id = Guid.NewGuid(),
ShoppingCartId = shoppingCartId,
ProductId = dto.ProductId,
Quantity = dto.Quantity,
Price = dto.Price,
PriceDiscount = dto.PriceDiscount
});
}
else
{
item.ProductId = dto.ProductId;
item.Quantity = dto.Quantity;
item.Price = dto.Price;
item.PriceDiscount = dto.PriceDiscount;
}
}
}
}
SetItemPriceCommandStrategyHandler
public class SetItemPriceCommandStrategyHandler : ICommandStrategyHandler<SetItemPriceCommandStrategy>
{
private readonly IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails> getProductDetailsQuery;
public SetItemPriceCommandStrategyHandler(
IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails> getProductDetailsQuery)
{
if (getProductDetailsQuery == null)
throw new ArgumentNullException("getProductDetailsQuery");
this.getProductDetailsQuery = getProductDetailsQuery;
}
public void Handle(SetItemPriceCommandStrategy command)
{
var shoppingCartItem = command.ShoppingCartItem;
var product = getProductDetailsQuery.Handle(new GetProductDetailsDataQuery { ProductId = shoppingCartItem.ProductId });
// TODO: For products with custom calculations, need to use selections on shopping cart item
// as well as custom formula and pricing points from product to calculate the item price.
shoppingCartItem.Price = product.Price;
}
}
GetProductDetailsDataQueryHandler
public class GetProductDetailsDataQueryHandler : IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails>
{
private readonly IAppContext context;
public GetProductDetailsDataQueryHandler(IAppContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public ProductDetails Handle(GetProductDetailsDataQuery query)
{
return (from product in context.Products
where product.Id == query.ProductId
select new ProductDetails
{
Id = product.Id,
Name = product.Name,
Price = product.Price
}).FirstOrDefault();
}
}
SetTotalsCommandStrategyHandler
public class SetTotalsCommandStrategyHandler : ICommandStrategyHandler<SetTotalsCommandStrategy>
{
private readonly ICommandStrategyHandler<SetDiscountsCommandStrategy> setDiscountsCommand;
private readonly ICommandStrategyHandler<SetSalesTaxCommandStrategy> setSalesTaxCommand;
public SetTotalsCommandStrategyHandler(
ICommandStrategyHandler<SetDiscountsCommandStrategy> setDiscountsCommand,
ICommandStrategyHandler<SetSalesTaxCommandStrategy> setSalesTaxCommand
)
{
if (setDiscountsCommand == null)
throw new ArgumentNullException("setDiscountsCommand");
if (setSalesTaxCommand == null)
throw new ArgumentNullException("setSalesTaxCommand");
this.setDiscountsCommand = setDiscountsCommand;
this.setSalesTaxCommand = setSalesTaxCommand;
}
public void Handle(SetTotalsCommandStrategy command)
{
var shoppingCart = command.ShoppingCart;
// Important: Discounts must be calculated before sales tax to ensure the discount is applied
// to the subtotal before tax is calculated.
setDiscountsCommand.Handle(new SetDiscountsCommandStrategy { ShoppingCart = shoppingCart });
setSalesTaxCommand.Handle(new SetSalesTaxCommandStrategy { ShoppingCart = shoppingCart });
}
}
SetDiscountsCommandStrategyHandler
public class SetDiscountsCommandStrategyHandler : ICommandStrategyHandler<SetDiscountsCommandStrategy>
{
public void Handle(SetDiscountsCommandStrategy command)
{
var shoppingCart = command.ShoppingCart;
// TODO: Set discounts according to business rules
foreach (var item in shoppingCart.Items)
{
item.PriceDiscount = 0;
}
shoppingCart.SubtotalDiscounts = 0;
shoppingCart.SalesTaxDiscounts = 0;
shoppingCart.ShippingDiscounts = 0;
}
}
SetSalesTaxCommandStrategyHandler
public class SetSalesTaxCommandStrategyHandler : ICommandStrategyHandler<SetSalesTaxCommandStrategy>
{
public void Handle(SetSalesTaxCommandStrategy command)
{
var shoppingCart = command.ShoppingCart;
var postalCode = command.ShoppingCart.ShippingPostalCode;
bool isInCalifornia = !string.IsNullOrEmpty(postalCode) ?
// Matches 90000 to 96200
Regex.IsMatch(postalCode, #"^9(?:[0-5]\d{3}|6[0-1]\d{2}|6200)(?:-?(?:\d{4}))?$") :
false;
if (isInCalifornia)
{
var subtotal = shoppingCart.GetSubtotal();
// Rule for California - charge a flat 7.75% if the zip code is in California
var salesTax = subtotal * 0.0775M;
shoppingCart.SalesTax = salesTax;
}
}
}
Do note that there is no shipping calculation in this workflow. This is primarily because the shipping calculation may depend on external APIs and it may take some time to return. Therefore, I am planning to make the AddToCart workflow a step that runs instantaneously when an item is added and make a CalculateShippingAndTax workflow that happens after the fact that updates the UI again after the totals have been retrieved from their (possibly external) sources, which might take time.
Does this solve the problem? Yes, it does fix the real-world problems I was having when commands need to depend on queries.
However, it feels like this really only separates queries from commands conceptually. Physically, they still depend on one another unless you only look at the IDataCommand and IDataQuery abstractions that only depend on ApplicationDbContext. I am not sure if this is the intent of qujck or not. I am also uncertain if this solves the bigger issue of the design being transferable to CQRS or not, but since it is not something I am planning for I am not that concerned about it.
There are always trade offs to consider between conflicting design principles. The way to resolve it is to look at the underlying reasons behind the principles. In this case, being unable to run a query without running the command is problematic, but being unable to run a command without running the query is generally harmless. As long as there's a way to run the query standalone, I see no reason not to add the query result to the command, especially if done something like this:
QueryResult command()
{
// do command stuff
return query();
}

Generic repository pattern and multiple selects

I am trying to learn the repository pattern and looking at a generic repository I cannot see how to handle customized select statements. For example, using this article the author uses a select by ID and a select all.
public interface IGenericRepository<T> where T:class
{
IEnumerable<T> SelectAll();
T SelectByID(object id);
void Insert(T obj);
void Update(T obj);
void Delete(object id);
void Save();
}
Later the article the IGenericRepository interface is implemented using Northwind. Then that is used to create a Customer controller.
public class CustomerController : Controller
{
private IGenericRepository<Customer> repository = null;
public CustomerController()
{
this.repository = new GenericRepository<Customer>();
}
...
This would handle selecting a list of all Customers or for one Customer by ID but where I get stuck is some more real world examples like "select all Customers for a client" or "select all Customers for a region". Plus, you could have another controller based on a different entity that would filter on different attributes. I assume I'm missing something basic. If the user interface needed to present the Customer entity (or any other entity) by various filters, how would this be done by sticking with one generic repository?
Here you go; to handle any select criteria apart from the Id, you can add Where method
like below
public interface IGenericRepository<T> where T:class
{
IEnumerable<T> SelectAll();
T SelectByID(object id);
IEnumerable<T> Where(Expression<Func<T,bool>> predicate)// this one
void Insert(T obj);
void Update(T obj);
void Delete(object id);
void Save();
}
Now in the Where method implementation do it like this
public IEnumerable<T> Where(Expression<Func<T,bool>> predicate)
{
return _objectSet.Where(predicate).AsEnumerable();
}
Here _objectSet in created in repository constructor like this :
public Repository(ObjectContext context)
{
_context = context;
_objectSet = _context.CreateObjectSet<T>();
}
public CustomerController()
{
_context = new NorthwindEntities();
_reporsitory = new Repository<Customer>(_context);
}
Use of Where method like
reporsitory.Where(c=>c.Country=="Canada").ToList();
For full reference see this project on codeplex (download /browse source code)
https://efgenericrepository.codeplex.com/SourceControl/latest
I think the implementation of the GenericRepository should somehow be able to return the IQueryable of current entity, like adding Get() method.
protected IQueryable<T> Get() // Notice that the access modifier is protected.
{
return table;
}
Then you could just create a derived class from GenericRepository and add a select method that accepts the Filter class.
public class CustomerRepository : GenericRepository<Customer>
{
public IEnumerable<T> SelectAll(CustomerFilter filter){ .. }
}
The filter class contains 2 filters.
public class CustomerFilter
{
public int? ClientId { get; set; }
public int? RegionId { get; set; }
}
Then the SelectAll implementation would be.
public IEnumerable<T> SelectAll(CustomerFilter filter)
{
var query = Get();
if (filter == null)
{
return query;
}
if (filter.ClientId.HasValue)
{
query = query.Where(q => q.ClientId == filter.ClientId.Value);
}
if (filter.RegionId.HasValue)
{
query = query.Where(q => q.RegionId == filter.RegionId.Value);
}
return query;
}
In the controller, calling it like.
public ActionResult Get(int? clientId, int? regionId)
{
var filter = new CustomerFilter { ClientId = clientId, RegionId = regionId };
var customers = _repository.SelectAll(filter);
return View();
}
You might need to see this post as your reference.
An approach I've seen in one asp.net mvc based mission critical app, is to use the generic interface as defined in the question. Then there is an abstract class that implements that interface. And there is one more repository class that inherits the abstract class, which has all methods specific to that class.
public interface IGenericRepository<T> where T:class
{
...
}
public abstract class GenericRepository<T> : IGenericRepository where T:class
{
...
}
And the CustomerRepository class
public class CustomerRepository : GenericRepository<Customer>
{
//add method specific to Customer like select Customers in specific country
}
And in the controller
public class CustomerController : Controller
{
private CustomerRepository repository = null;
public CustomerController()
{
this.repository = new CustomerRepository();
}
...

using stored procedure in entity framework

I am using asp.net mvc 5 and C# with Entity Framework... I have model and domain classes for function... now I need to use stored procedure.... which I am struggling at the movement.
I am following code first existing database and I have stored procedure written there. My question is how I can call that stored procedure in my web application.
Stored procedure:
ALTER PROCEDURE [dbo].[GetFunctionByID](
#FunctionId INT
)
AS
BEGIN
SELECT *
FROM Functions As Fun
WHERE Function_ID = #FunctionId
END
Domain class:
public class Functions
{
public Functions()
{
}
public int Function_ID { get; set; }
public string Title { get; set; }
public int Hierarchy_level { get; set; }
}
Function model:
[Table("Functions")]
public class App_Functions
{
public App_Functions()
{
}
[Key]
public int Function_ID { get; set; }
[StringLength(50)]
[Required]
public string Title { get; set; }
public int Hierarchy_level { get; set; }
//public virtual ICollection<App_Controllers> App_Controllers { get; set; }*/
}
BaseContext:
public class BaseContext<TContext> : DbContext where TContext : DbContext
{
static BaseContext()
{
Database.SetInitializer<TContext>(null);
}
protected BaseContext()
: base("name = ApplicationDbConnection")
{ }
}
Function context:
public class FunctionsContext : BaseContext<FunctionsContext>
{
public DbSet<App_Functions> Functions { get; set; }
}
You need to create a model class that contains all stored procedure properties like below.
Also because Entity Framework model class needs primary key, you can create a fake key by using Guid.
public class GetFunctionByID
{
[Key]
public Guid? GetFunctionByID { get; set; }
// All the other properties.
}
then register the GetFunctionByID model class in your DbContext.
public class FunctionsContext : BaseContext<FunctionsContext>
{
public DbSet<App_Functions> Functions { get; set; }
public DbSet<GetFunctionByID> GetFunctionByIds {get;set;}
}
When you call your stored procedure, just see below:
var functionId = yourIdParameter;
var result = db.Database.SqlQuery<GetFunctionByID>("GetFunctionByID #FunctionId", new SqlParameter("#FunctionId", functionId)).ToList());
After importing stored procedure, you can create object of stored procedure pass the parameter like function
using (var entity = new FunctionsContext())
{
var DBdata = entity.GetFunctionByID(5).ToList<Functions>();
}
or you can also use SqlQuery
using (var entity = new FunctionsContext())
{
var Parameter = new SqlParameter {
ParameterName = "FunctionId",
Value = 5
};
var DBdata = entity.Database.SqlQuery<Course>("exec GetFunctionByID #FunctionId ", Parameter).ToList<Functions>();
}
You can call a stored procedure using SqlQuery (See here)
// Prepare the query
var query = context.Functions.SqlQuery(
"EXEC [dbo].[GetFunctionByID] #p1",
new SqlParameter("p1", 200));
// add NoTracking() if required
// Fetch the results
var result = query.ToList();
// Add some tenants to context so we have something for the procedure to return!
AddTenentsToContext(Context);
// ACT
// Get the results by calling the stored procedure from the context extention method
var results = Context.ExecuteStoredProcedure(procedure);
// ASSERT
Assert.AreEqual(expectedCount, results.Count);
}
Mindless passenger has a project that allows you to call a stored proc from entity frame work like this....
using (testentities te = new testentities())
{
//-------------------------------------------------------------
// Simple stored proc
//-------------------------------------------------------------
var parms1 = new testone() { inparm = "abcd" };
var results1 = te.CallStoredProc<testone>(te.testoneproc, parms1);
var r1 = results1.ToList<TestOneResultSet>();
}
... and I am working on a stored procedure framework (here) which you can call like in one of my test methods shown below...
[TestClass]
public class TenantDataBasedTests : BaseIntegrationTest
{
[TestMethod]
public void GetTenantForName_ReturnsOneRecord()
{
// ARRANGE
const int expectedCount = 1;
const string expectedName = "Me";
// Build the paraemeters object
var parameters = new GetTenantForTenantNameParameters
{
TenantName = expectedName
};
// get an instance of the stored procedure passing the parameters
var procedure = new GetTenantForTenantNameProcedure(parameters);
// Initialise the procedure name and schema from procedure attributes
procedure.InitializeFromAttributes();
// Add some tenants to context so we have something for the procedure to return!
AddTenentsToContext(Context);
// ACT
// Get the results by calling the stored procedure from the context extention method
var results = Context.ExecuteStoredProcedure(procedure);
// ASSERT
Assert.AreEqual(expectedCount, results.Count);
}
}
internal class GetTenantForTenantNameParameters
{
[Name("TenantName")]
[Size(100)]
[ParameterDbType(SqlDbType.VarChar)]
public string TenantName { get; set; }
}
[Schema("app")]
[Name("Tenant_GetForTenantName")]
internal class GetTenantForTenantNameProcedure
: StoredProcedureBase<TenantResultRow, GetTenantForTenantNameParameters>
{
public GetTenantForTenantNameProcedure(
GetTenantForTenantNameParameters parameters)
: base(parameters)
{
}
}
If either of those two approaches are any good?
Simple. Just instantiate your entity, set it to an object and pass it to your view in your controller.
Entity
VehicleInfoEntities db = new VehicleInfoEntities();
Stored Procedure
dbo.prcGetMakes()
or
you can add any parameters in your stored procedure inside the brackets ()
dbo.prcGetMakes("BMW")
Controller
public class HomeController : Controller
{
VehicleInfoEntities db = new VehicleInfoEntities();
public ActionResult Index()
{
var makes = db.prcGetMakes(null);
return View(makes);
}
}

Intranet Application - Data Access in separate project

I have an MVC intranet application which uses EF 6. I have setup the DataAccess project in a separate class library which has EF 6 referenced. I have an entity which implements an interface:
public interface IAuditable
{
DateTime CreatedDateTime { get; set; }
string CreatedBy { get; set; }
}
public class Collection : IAuditable
{
// Properties
}
However, in the SaveChanges method I obviously don't have access to HttpContext.Current.User.Identity.Name as it is in a separate class library, so I was wondering how one would set this in SaveChanges?
public override int SaveChanges()
{
var addedEntries = ChangeTracker.Entries().Where(x => x.State == EntityState.Added);
foreach (var dbEntityEntry in addedEntries)
{
var entity = dbEntityEntry.Entity as IAuditable;
if (entity != null)
{
entity.CreatedDateTime = DateTime.Now;
// how do I set entity.CreatedBy = HttpContext.Current.User.Identity.Name?
}
}
return base.SaveChanges();
}
Edit
Following on from #CodeCaster solution, I have the following:
[BreezeController]
public class BreezeController : ApiController
{
private readonly BTNIntranetRepository _repository;
public BreezeController(BTNIntranetRepository repository)
{
_repository = repository;
_repository.LoggedInUser = HttpContext.Current.User.Identity.Name;
}
// Methods
}
But HttpContext.Current.User is null
This can be solved in many ways.
You're not really showing relevant code, but you can for example give the library class you expose a public string LoggedInUser (or ActingUser or give it a name) property which you set when instantiating it:
public class SomeController : Controller
{
private IDataSource _dataSource;
public SomeController(IDataSource dataSource)
{
_dataSource = dataSource;
_dataSource.LoggedInUser = HttpContext.Current.User.Identity.Name
}
}
You can then simply use that property in your IDataSource.SaveChanges() method:
public override int SaveChanges()
{
// ...
entity.CreatedBy = this.LoggedInUser;
}

Entity framework savechanges error

I have a wizard step in which a user fills in fields. I then use json to save the values into my database for each wizard step.
However, in my repository I have my savechanges(). But it wont save the changes, instead it throws an error:
Entities in 'NKImodeledmxContainer.SelectedQuestion' participate in the 'QuestionSelectedQuestion' relationship. 0 related 'Question' were found. 1 'Question' is expected.
Anyone know how to get rid of the error? Do I have to get the ID from Question and save it aswell to my database or can I change something in EF so the error message is not getting thrown?
This is my post in my controller:
[HttpPost]
public JsonResult AnswerForm(int id, SelectedQuestionViewModel model)
{
bool result = false;
var goalCardQuestionAnswer = new GoalCardQuestionAnswer();
goalCardQuestionAnswer.SelectedQuestion = new SelectedQuestion();
goalCardQuestionAnswer.SelectedQuestion.Id = model.QuestionID;
goalCardQuestionAnswer.Comment = model.Comment;
goalCardQuestionAnswer.Grade = model.Grade;
if (goalCardQuestionAnswer.Grade != null)
{
answerNKIRepository.SaveQuestionAnswer(goalCardQuestionAnswer);
answerNKIRepository.Save();
result = true;
return Json(result);
}
answerNKIRepository.SaveQuestionAnswer(goalCardQuestionAnswer);
answerNKIRepository.Save();
return Json(result);
}
My Repository
public class AnswerNKIRepository
{
private readonly NKImodeledmxContainer db = new NKImodeledmxContainer();
public List<SelectedQuestion> GetAllSelectedQuestionsByGoalCardId(int goalCardId)
{
return db.SelectedQuestion.Where(question => question.GoalCard.Id == goalCardId).ToList();
}
public void SaveQuestionAnswer(GoalCardQuestionAnswer goalCardQuestionAnswer)
{
db.GoalCardQuestionAnswer.AddObject(goalCardQuestionAnswer);
}
public void Save()
{
db.SaveChanges();
}
}
This is my ViewModel:
public class SelectedQuestionViewModel
{
public int? Grade { get; set; }
public string Comment { get; set; }
public string SelectedQuestionText { get; set; }
public int QuestionID { get; set; }
}
This is my database model:
The exception complains that SelectedQuestion.Question is a required navigation property but you don't set this property in your code. Try to load the question by Id from the repository and set it to the SelectedQuestion.Question reference: Replace this line ...
goalCardQuestionAnswer.SelectedQuestion.Id = model.QuestionID;
...by...
goalCardQuestionAnswer.SelectedQuestion.Question =
answerNKIRepository.GetQuestionById(model.QuestionID);
And in your repository add the method:
public Question GetQuestionById(int id)
{
return db.Question.Single(q => q.Id == id);
}

Resources