I am searching for a way to make generic action methods that can be inherited by multiple controllers, so I don't have to repeat the same method in MVC for different controllers and tables. I think this would be applicable to a lot of the CRUD stuff I have to frequently do for multiple classes.
For example, here's the code I'd like to duplicate:
public ActionResult ToggleQC(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
BACTERIA_EVW bacteria = db.BACTERIA_EVW.Find(id);
if (bacteria == null)
{
return HttpNotFound();
}
//add/remove QC status (switch to whichever one it isn't right now)
if (bacteria.QCOn == null) //if it hasn't been QCed
{
bacteria.QCOn = DateTime.Now;
bacteria.QCBy = User.Identity.Name;
}
else //if it has been QCed and they are undoing it
{
bacteria.QCBy = null;
bacteria.QCOn = null;
}
//save changes
db.Entry(bacteria).State = EntityState.Modified;
db.SaveChanges();
//return updated QC status partial
return PartialView("_QCStatus", bacteria);
}
I need to do the same thing in a chemistry controller, but I'd rather not repeat the whole thing and just change one part. Is it possible to pass the model type into the method as a parameter, to replace BACTERIA_EVW? How would I do that?
I apologize if this is something really basic; I may not know the right terms to look for. I've searched for generic action methods but I haven't found any answers, although there was something about generic controllers - do I need to make a generic controller class somehow to include a method like this?
Thanks very much.
If you just care about removing code redundancy you can create a generic controller with that method and inherit other controllers from that:
namespace BaseControllers
{
public class CoolController
{
public virtual ViewResult Get()
{
var awesomeModel = new object();
return View(awesomeModel);
}
}
}
And in your child controller:
public class CoolController : BaseControllers.CoolController
{
public override ViewResult Get()
{
var ignoredResult = base.Get();
// ViewData.Model now refers to awesomeModel
return View("NotGet");
}
}
Same can happen for other CRUD operations.
Related
I am building a web app using ASP.NET MVC5. I have logic for validating the route value similar to the following.
[HttpGet]
public ActionResult Books(string name)
{
if(!CheckBook(name))
{
return RedirectToAction(Index);
}
return View(model:name);
}
private bool CheckBook(string name)
{
var exists = false;
var allBooks = Library.Books; // Library is a static class and Books returns a list with all the book names
foreach(var book in allBooks)
{
if(book == name)
{
exists = true;
}
}
return exists;
}
Is it a bad practice to validate the route value in the controller? Should I create a folder such as validators and add validation functions there? Or is this where I create services? I learned that I shouldn't add business logic in controllers, so I am unsure.
First off it's OK to put simple Business Logic directly in a controller, as you can test it with DI. Putting the Business Logic in a library with a Domain Specific Language is obviously better.
Let me help you out with a few things:
Because you need to return View(model:name); and the name is based on the Route Value you will need to validate it exists.
Because the Book lookup is related to the Book controller I'd keep the "simple" logic in the controller.
When we talk about Validators, we tend to be talking about parsing form inputs (and validating at both front and back-ends). See this example with a link to a dozen more examples: https://stackoverflow.com/a/51851312/495455
In this case where you need to interrogate routes, headers and query strings you can keep the logic in the controller, or if its called twice or more move it to a library class for polymorphic use.
The Lookup method:
OP:
private bool CheckBook(string name)
{
var exists = false;
var allBooks = Library.Books; // Library is a static class and Books returns a list with all the book names
foreach(var book in allBooks)
{
if(book == name)
{
exists = true;
}
}
return exists;
}
Better:
private bool CheckBook(string name)
{
var allBooks = Library.Books;
foreach(var book in allBooks)
{
if(book == name)
{
return true; //Don't keep iterating to the end when we know its true!
}
}
return false;
}
Better:
private bool CheckBookExists(string name)
{
var bookExists = Library.Books.Contains(name);
return bookExists;
}
rI need some advice on where to run a calculation on data.
I have a viewmodel that contains all the fields that I need for my calculation and I created the following for one of my calculations:
public class CommissionVM
{
public int? LoanAmountLock { get; set; } // from loan table
public decimal BranchRev { get; set; } // from revenue table
public decimal BranchProcessFee { get; set; } // from revenue table
public decimal BranchGrossTotal
{
get
{
return Convert.ToDecimal(LoanAmountLock * (BranchRev/ 100) + BranchProcessFee);
}
}
}
I tried to use the Model.BranchGrossTotal in my view, but it is returning 0. I think I have an order-of-operations problem. The values LoanAmountLock, BranchRev, and BranchProcessFee are returned as the results of a query:
public ActionResult Reconcile(int? id, string RevenueLoanType)
{
var model = new CommissionVM()
{
Loan = db.Loan.FirstOrDefault(a => a.id == id ),
BranchRevenues = db.BranchRevenues.FirstOrDefault(a => a.RevenueLoanType == RevenueLoanType),
};
return View(model);
}
I originally was able to get these calculations to work by doing all the math in the controller after I populate the viewmodel with the query, but there will be about 10 calculations, and from what I understand, I shouldn't clutter up my controller with business logic.
What is the best solution for this? Do I need to create another class for the calculations? If so, how do I populate that class with my data and use it in my controller?
EDIT: I am not sure how to set up the business classes and use them in the controller. Can anyone point me in the direction of a tutorial?
You should not do the calculation in your controller nor in your view model. You should do it in the Business Layer. Think about View Models are really simple classes that contain data to be displayed to the user, that's it.
Regarding the calculation, you should convert one of the terms to decimal, not the result of the calculation. If you divide integers, you get an integer.
You could create a class and call it CommissionService for example. That class should call your Data Layer to get the data, do any extra calculation and return the data (maybe a DTO) to the controller. The controller should create View Models based on the DTO and send them to the view.
Read these articles:
1) https://msdn.microsoft.com/en-us/library/hh404093.aspx
2) http://www.asp.net/mvc/overview/older-versions-1/models-%28data%29/validating-with-a-service-layer-cs
3) http://blog.diatomenterprises.com/asp-net-mvc-business-logic-as-a-separate-layer/
4) http://sampathloku.blogspot.com.ar/2012/10/how-to-use-viewmodel-with-aspnet-mvc.html
I don't like calculations on my view models -- you can't reuse the calculation easily elsewhere and it is harder to test and debug. Create separate classes to do business logic.
Your business logic classes can either return your view models or return values you use to populate those models. The trade-off is ease of use with reusability.
I generally favor returning the value rather than a big object so my services are more reusable.
Controller
public class BranchController : Controller
{
private IBusinessService service;
public BranchController()
{
this.service = new BusinessService(db);
}
public ActionResult Reconcile(int? id, string RevenueLoanType)
{
var model = new CommissionVM
{
BranchGrossTotal = this.service.GetBranchGrossTotal(id, RevenueLoanType),
...
};
return View(model);
}
}
Service
You can make any number of these and your controllers would use them as needed. If you need a query you should pass the DbContext instance or you may have problems with related entities on separate contexts.
public interface IBusinessService
{
decimal GetBranchGrossTotal(int id, string revenueLoanType);
}
public class BusinessService : IBusinessService
{
private DbContext db;
public BusinessService(DbContext db)
{
this.db = db;
}
public decimal GetBranchGrossTotal(int id, string revenueLoanType)
{
var branch = db.Branch.First(b => b.Id == id);
// do stuff
return total;
}
}
You could fully-populate and return a view model in your GetBranchGrossTotal() if you choose.
First of all, the properties you are assigning to your CommissionVM on your controller do not match the ones declared on your model. You assign Loan and BranchRevenues, when you have only LoanAmountLock and BranchRevs available on your model.
Please notice that the Loan property is an object itself, and the LoanAmountLock must be retrieved from this object (Loan.LoanAmountLock). The same happens with the BranchRevenues object. You should assign the BranchRevs to the respective property of the BranchRevenues object as needed. If you do not do this, then the values will default to 0 and when trying to calculate the BranchGrossTotal it will obviously be 0.
Another reason, assuming that you are correctly populating your model properties, is that the FirstOrDefault method, renders null values because there is no such entity. This will result also in the BranchGrossTotal to be 0.
You are right that you do not need to clutter your controller neither with calculations nor with db access. I would create a business class ComissionBusiness and instantiate it at the top of your controller. This class would have a method which performs all calculations. You should move the Reconcile method to your new business class method and call it on the reconcile action. Something like (excuse the lack of syntax)
public MyController : Controller {
public ComissionBusiness comissionBusiness;
public MyController(){
comissionBusiness = new ComissionBusiness();
}
public ActionResult Reconcile(int? id, string RevenueLoanType)
{
var model = comissionBusiness.Reconcile(id, revenueLoanType);
return View(model);
}
}
I've found that I have a lot of repeated code in all of my actions, and want to know the best way to avoid this. Say for example that each logged on user belongs to a school and I need to access this SchoolId in almost every action.
They way I have it now almost every action will have a repeated database hit and need to reference my userService class...something like:
public ActionResult Index()
{
var schoolId = userService.GetSchoolId(User.Identity.GetUserId());
var textBooksForSchool = textBookService.GetBooks(schoolId);
...
}
public ActionResult Delete()
{
var schoolId = userService.GetSchoolId(User.Identity.GetUserId());//all over the place
var textBooksForSchool = textBookService.DeleteBooks(schoolId);
...
}
I know that I can add the SchoolId to the claims but the syntax for returning it in every method is quite verbose (as far as I understand this avoids the db hit each time the claim is accessed?):
In GenerateIdentityAsync:
var claims = new Collection<Claim>
{
new Claim("SchoolId", User.SchoolId.ToString())
};
userIdentity.AddClaims(claims);
In Action:
var SchoolId = Convert.ToInt32((User as ClaimsPrincipal).Claims.First(x => x.Type == "SchoolId").Value);
Is there some kind of best practice here? Possibly storing the claim in a global variable on logon?
This is how I am doing...
Base Controller
public class BaseController : Controller
{
public AppUser CurrentUser
{
get
{
return new AppUser(this.User as ClaimsPrincipal);
}
}
}
Claims Principal
public class AppUser : ClaimsPrincipal
{
public AppUser(ClaimsPrincipal principal)
: base(principal)
{
}
public string Name
{
get
{
return this.FindFirst(ClaimTypes.Name).Value;
}
}
public string Email
{
get
{
return this.FindFirst(ClaimTypes.Email).Value;
}
}
}
In the other controller you can access the claim type just by doing
CurrentUser.Email
What about creating your own base controller that all your controllers inherit from that has SchoolId as a property and then creating an ActionFilter that casts each controller as that base controller and sets that value on every request? Then it will be available on all your actions but you only have to write the code once.
It will fire each request, so you might consider other techniques for minimizing the number of times you have to look up the value, but this mechanism is a way to solve your code duplication issue.
I really like the extension method approach:
public static int SchoolId(this IPrincipal principal)
{
return Convert.ToInt32((principal as ClaimsPrincipal).Claims.First(x => x.Type == "SchoolId").Value);
}
Action:
var textBooksForSchool = textBookService.GetBooks(User.SchoolId());
I have model class:
public class Person {
public int Id { get; set; }
...
}
and to see details about a person user can guess its' id
http://localhost:17697/Person/Details/2
they're just consecutive integers.
How can I tell Entity Framework to shuffle those ID to make them harder to guess?
If you don't want predictable IDs then you could use a Guid instead of int. "Shuffling" would over-complicate the process and it's not going to give you any protection.
Remember that if you're trying to secure a url, write proper security using authorization and filters. Security through obscurity does not actually secure anything
Personally, I utilize slugs in my URLs, rather than ids. Something like:
http://localhost:17697/Person/Details/john-doe
You then pull the object based on the slug:
db.People.SingleOrDefault(m => m.Slug == slug);
However, "security by obscurity" is not a good game plan. Making the ids "harder to guess", doesn't solve the problem of people accessing it who shouldn't. If the details should be protected, then implement authentication and specify an authorization policy for the action.
Late to the party, but since there isn't much about using HashIds within ASP.NET MVC I'll share my solution using a custom ModelBinder and a BaseModel class. The end route looks something like /example/voQ/details.
First you need a model, that your existing models can extend from and generate a HashId;
public class abstract BaseModel
{
private static readonly Hashids __hashId = new Hashids("seed", 2);
public Id { get; set; }
[NotMapped]
public HashId
{
get { return BaseModel.__hashId.Encode(this.Id); }
}
}
The binder needs registering in Global.asax for each model:
ModelBinders.Binders.Add(typeof(ExampleModel), new ControllerModelBinder<ExampleModel>());
Then the action can use the model directly without worrying about the hash id:
public ActionResult Details(ExampleModel model)
{
return View(model);
}
Setting up a link is the same, but rather than passing the Id, you need to use the HashId property from the BaseModel.
#Url.Action("Details", new { id = item.HashId })
Finally the the model binder:
public class ControllerModelBinder<T> : DefaultModelBinder
where T : BaseModel
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(T))
{
string hash = bindingContext.ValueProvider.GetValue("id").RawValue.ToString();
if (!string.IsNullOrWhiteSpace(hash))
{
int id = HashIdHelper.ToInt(hash);
if (id > 0)
{
using (ApplicationContext context = new ApplicationContext())
{
DbRawSqlQuery<T> query = context.Database.SqlQuery<T>(string.Format("SELECT * FROM {0} WHERE id = #Id LIMIT 1", EntityHelper.GetTableName<T>(context)), new MySqlParameter("#Id", id));
try
{
T model = query.Cast<T>().FirstOrDefault();
if (model != null)
{
return model;
}
}
catch (Exception ex)
{
if (ex is ArgumentNullException || ex is InvalidCastException)
{
return base.BindModel(controllerContext, bindingContext);
}
throw;
}
}
}
}
}
return base.BindModel(controllerContext, bindingContext);
}
}
You can use HttpServerUtility.UrlTokenEncode and HttpServerUtility.UrlTokenDecode
Encode uses base64 encoding, but replaces URL unfriendly characters.
There's a similar answer in a previous SO question. See the accepted answer.
MSDN Reference
I want to improve current implementation of the ASP.NET MVC Framework.
Current code:
routes.MapRoute(null, "I-want-to-fly", new { controller = "Airport", action = "Fly" });
public class AirportModel
{
public List<Plane> Planes { get; private set; }
public List<Pilot> Pilots { get; private set; }
public void AddFly(Plane plane, Pilot pilot, Passenger passenger)
{
// . . .
}
}
public class AirportController
{
private AirportModel model;
[HttpGet]
public ViewResult Fly(string from, string to)
{
var planes = return (from p in model.Planes
where p.CityFrom == from && p.CityTo == to
select p).ToList();
return View(planes);
}
[HttpPost]
public ActionResult Fly(Plane plane, Passenger passenger, DateTime time)
{
if (!(ModelState.IsValid && plane.TimeOfDeparture == time))
return View();
var pilot = (from p in model.Pilots
where p.Free && p.CanAviate(plane.Id)
select p).First();
model.AddFly(plane, pilot, passenger);
return RedirectToAction("Succeed");
}
}
My proposal:
routes.MapRoute(null, "I-want-to-fly", new { model = "Airport", action = "Fly" });
public class AirportModel
{
private List<Plane> planes;
private List<Pilot> pilots;
private void AddFly(Plane plane, Pilot pilot, Passenger passenger)
{
// . . .
}
[HttpGet]
public ViewResult Fly(string from, string to)
{
var planes = return (from p in model.Planes
where p.CityFrom == from && p.CityTo == to
select p).ToList();
return View(suitablePlanes);
}
[HttpPost]
public ActionResult Fly(Plane plane, Passenger passenger, DateTime time)
{
if (!(ModelState.IsValid && new PlaneController().CanFly(plane, time)))
return View();
var pilot = (from p in pilots
where p.Free && p.CanAviate(plane.Id)
select p).First();
AddFly(plane, pilot, passenger);
return RedirectToAction("Succeed");
}
}
public static class PlaneController
{
public static bool CanFly(Plane plane, DateTime time)
{
return plane.TimeOfDeparture == time; // it will be more complex
}
}
You see, in such way we don't need excessive count of controllers and their methods. Model would create controller only by perforce: mostly to verify user input (not input validation, business validation).
What do you think, can this idea have a continuation? Or, what is wrong with it?
Thanks for your replies!
UPDATE: I noticed, that we need to replace implementations of controller and view as a result of changing the model's state (mostly). So, if model causes to change the implementation, why model cannot do it?
UPDATE 2: It seems to me I explained incorrectly. I don't want model to do all work, of course no! I try to say, that not controller should decide what to do with model and what view is the most suitable for this user request.
Doesn't it strange, that model doesn't know how to visualize itself, but some controller knows?
Doesn't it strange, than we need controller for the GET request, where there is nothing to control?
I try to remove those strangenesses.
UPDATE 3: I understand that it cannot be applied anywhere. The main question is: can it improve some part of current implementations of MVC ? Mostly I'm interested in ASP.NET MVC -- can we
remove redundant controllers or some its methods
work directly with models
using this idea? Is it possible and what are the problems of this idea?
Found problems:
More strong connection between model and view/controller -- but currently I don't think it's a problem. Actually it shows that views and controllers were created in help for the major element -- model.
UPDATE 4: I changed the code, showing "before/after". Maybe this example will be better.
Doesn't this just violate the whole idea of MVC? Your model is separated from your controller and your view. In this way (the way you propose) you would not be able to replace your model by another implementation, or your controller for that matter.
updated I:
You could of course let your model do the part of the controller as well, but from that moment on you're not talking about the MVC design pattern anymore. For MVC, the model does and should not now about the view. That's the controllers job.
The controller receives user input and initiates a response by making calls on model objects. A controller accepts input from the user and instructs the model and viewport to perform actions based on that input.
In the MVC pattern, the Model isn't just fixed to your database model, it could be a combination of your database model and a repository pattern as well, where you implement your business logic.
The biggest problem I see with your proposal is that it makes code non-reusable. I get a model that is tightly coupled with it's views which I really don't want if I want to reuse the model in whatever way I might want to.
Update II
I think your are being mislead by the actual word Controller, I had that thought for a while and your latest comment sort of confirms this for me
Controllers are some objects, that check correspondence of user input to business-logic.
Controllers act upon user input, they might check the user input but their responsibility for checking validity stops there. Business logic goes in the Model (again, the Model as defined by the MVC pattern, not the model as in datamodel). Their main purpose is deciding what View to display.
Also from one of your latest comments:
How do you think, if [asp.net mvc] would be developed in my way, would it solve problem of redundant controllers?
Asp.Net MVC follows the MVC design pattern. Your proposal does not. It seem more like a ModelControlled View pattern, just to coin a name. Also, there are no redundant controllers, the controllers are no problem, they are an integral part of the solution.
And an effort to simplistically clarify what I mean with a code example:
namespace DataProject.Model
{
public class AirportModel
{
public List<Plane> Planes { get; set; }
public List<Pilot> Pilots { get; set; }
public List<Passenger> Passengers { get; set; }
public List<Flight> Flights { get; set; }
}
}
namespace SomeProject.Repository
{
public class AirportRepository
{
private DataProject.Model.AirportModel model;
//constructor sets the model somehow
public bool AddFlight(Plane plane, List<Passenger> passengers, DateTime time)
{
//Business logic
if (plane.TimeOfDeparture != time) return false;
var pilot = (from p in model.Pilots
where p.Free &&
p.CanAviate(plane.Id)
select p).FirstOrDefault();
//More Business logic
if (pilot == null) return false;
//Add plane, pilot and passenger to database
model.Flights.add(new Flight{Pilot = pilot, Plane = plane, Passengers = passengers});
//Even here you could decide to do some error handling, since you could get errors from database restrictions
model.Save();
return true;
}
public List<Planes> GetPlanes(string from, string to)
{
return (from p in model.Planes
where p.CityFrom == from && p.CityTo == to
select p).ToList();
}
}
}
namespace MVCApp.Controllers
{
public class AirportController
{
private SomeProject.Repository.AirportRepository repository;
[HttpGet]
public ViewResult Fly(string from, string to)
{
var viewModel = repository.GetPlanes(from, to);
return View(viewModel);
}
[HttpPost]
public ActionResult Fly(Plane plane, List<Passenger> passengers, DateTime time)
{
if (!ModelState.IsValid) return View();
if (!repository.AddFlight(plane, pilot, passenger)) return View();
return RedirectToAction("Succeed");
}
}
}
No offense intended but how exactly is this an improvement?
So you've made a class called a PersonModel that isn't really doing "model things" at all - it is doing the work that Controllers do - you've got it handling gets and posts and calling out for the display of Views and then you've got a static "Controller" that really controlling nothing and is concerning itself with business logic. Honestly, I don't get how this is an improvement.
A concrete example of is you've got a controller checking whether Age >= 18, which is a very "business rules" thing for a controller to be doing. That's not the purpose of a controller. That's the job of a model object - to concern itself with things like business logic. Controllers, as one person put it, are more of an electronic curator. In your example, you've relegated it to something far less than a curator.
There are distinct roles that objects play in an MVC application. Views show us stuff and provide us with ways to interact with the application. Controllers handle the input coming from the View and serve up views that are needed. Models provides a place to put data and the logic and business rules that the model encompasses. Services handle things like persisting data to some store, like a DB.
You can do everything in one class without controllers at all but you want "separte of concerns"
So the controller is responsible for the http request and validation and the model is responsible only for the data.
You are the programmer then you could agree or disagree with MVC pattern.
But the pattern which you described doesn't support the separation of concern and it breaks out the whole idea about MVC
This has nothing to do with MVC.
This is 'MVNothing'
:)
Just kidding *_^