I have a page that is used to register people for training. The current page has three drop downs on it, one to select the person to register, one to select the course, and one to select the session that the course is offered. It is being requested that the page be modified to now have four groups of the Course and Session drop downs to allow for the user to register up to four classes for a person at a time. My problem arises on how to handle the form posting now. Before if a user tried to add a person to a course they were already registered for the Entity Framework model binding would catch the error and send the error message back to the form. I'm not sure how to implement this binding now that there are four instances to evaluate and save at the same time. I'm a newbie to MVC and EF so any help/direction would greatly be appreciated.
Edit: below is a piece of the orignial controller save function:
var AddToCourse = new Session_Registrant()
{
RegistrantID = RegistrantID,
SessionID = Session1,
RegistrantOrg = regOrg,
RegistrantTitle = title,
RegistedDate = DateTime.Now
};
//attempt to save
try
{
if (ModelState.IsValid)
{
db.AddToSession_Registrant(AddToCourse);
db.SaveChanges();
}
}
catch (DataException error)
{
if (error.InnerException != null)
{
if (error.InnerException.Message.Contains("UNIQUE KEY constraint"))
{
//adding custom error message to explain the failure
ModelState.AddModelError("", "Error! Registrant has already been added to this course session.");
}
}
else
{
ModelState.AddModelError("Error", error.Message);
}
}
I wonder if the problem is with the design of the function rather than the implementation. Should the problem be defined in terms of a one-to-many relationship between the registrant entity and the course/session entity.
I am also of the view that using exceptions as part of the design is bad practice and that this is a definition problem. Should you (your code) be asking questions like:
Is this a registered student
Does this student have a course already booked in this session
Is this student already registered on the course in a different session
In other words define your business rules, implement them and the problem should resolve itself into a SMOC (small matter of coding).
I am sorry if this response is not more directly helpful but I feel that this is the wrong starting point. Good luck anyway.
Related
I have been implementing Jhipster at my work and loving it. I was asked to implement a security validation that one user should not be allowed to edit the entity created by other user. For this I need two things:
First, in all entities, add a ManytoOne relation with User entity.
In Backend put a validation in controller while updating the entity to check if current logged in user is same as what is stored in DB. In front end also same logic to show/hide edit button.
I have done a POC for this and it works but is little ugly, check the code:
public ResponseEntity<Entry> updateEntry(#RequestBody Entry entry) throws URISyntaxException {
log.debug("REST request to update Entry : {}", entry);
if (entry.getId() == null) {
throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
}
//here is my logic
Optional<Entry> entry_db = entryRepository.findOneWithEagerRelationships(entry.getId());
if(!entry_db.get().getUser().getId().equals(userService.getUserWithAuthorities().get().getId())) {
//throw someexception
}
//
Entry result = entryRepository.save(entry);
return ResponseEntity.ok()
.headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, entry.getId().toString()))
.body(result);
}
Is there any better or OOTB way of doing this??? may be something in spring security i am not aware of??
Thanks for help!!
This is a job for Spring Security Expression-Based Access Control, in particular you could annotate your method with #PreFilter and a custom PermissionEvaluator you would implement with similar logic as in your POC. The PermissionEvaluator could be generic and applied to several entity types if you define an Owned interface that models the ownership with a method like User getOwner() and that all your entity classes would implement.
See https://docs.spring.io/spring-security/site/docs/current/reference/html5/#el-access
The annotated method should be in a service rather than in a resource controller.
Also, UserService alone will not help you in finding the current authenticated user, you should use JHipster's SecurityUtils first and then ÙserService if you need more data about it.
I'm new using entity framework, and I'm trying to insert into the DB.But I'm having an issue, because I need to only SaveChanges from objects of other 3 scopes. Like this:These are my three Actions that Add the objects into my entities:
public void AddEndereco(entidade_endereco entEndereco)
{
db.entidade_endereco.Add(entEndereco);
}
public void addContato(entidade_contato entContato)
{
db.entidade_contato.Add(entContato);
}
public void addBanco(entidade_banco entBanco)
{
db.entidade_banco.Add(entBanco);
}
And in this action I need to insert all the objects into my DB:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(entidade entidade, string Grupo, string Situacao)
{
if (ModelState.IsValid)
{
if (Grupo != "")
entidade.gre_codigo = Convert.ToInt32(Grupo);
if (Situacao != "")
entidade.sie_codigo = Convert.ToInt32(Situacao);
if (entidade.ver_ativo)
entidade.ent_ativo = "S";
else
entidade.ent_ativo = "N";
if (entidade.ver_cliente)
entidade.ent_cliente = "S";
else
entidade.ent_cliente = "N";
if (entidade.ver_fornecedor)
entidade.ent_fornecedor = "S";
else
entidade.ent_fornecedor = "N";
//ADDING ANOTHER OBJECT
db.entidades.Add(entidade);
//HERE IS WHERE I NEED TO SAVE ALL (entidade_endereco, entidade_contato, entidade_banco, entidade)
db.SaveChanges();
return RedirectToAction("Index");
}
return View(entidade);
}
But it is only saving the entidade object, the others don't exist anymore when db.SaveChanges() is executed.
How can I insert into the DB with objects that were added to my entity in other scopes?
If you really want to make this work as is, you would need to store either the Context (really bad idea) or Entities (slightly less bad) across requests. Session State jumps to mind, but using it can bring in a whole load of new pain.
Ideally, you should change your design to take advantage of the stateless nature of HTTP. Each action method should be a separate transaction, saving the data from it's execution when the method is done. If those separate entities only make sense when they are all saved together, then you need to create all of them within a single action and save them to the context together. Managing the boundaries of different business entities and when they are saved is a critical part of application design, I highly recommend you read about Aggregate Roots within Domain Driven Development. Even if you don't go the full DDD route, the Aggregate Root concept will be extremely helpful to you. The CQRS Journey from Microsoft gives an in-depth tutorial of these concepts (and many others)
Im not sure, if I got your question right (excuse my poor spanish). In the Action Create you only add "entidade" to your entidades collection, and so its the only one affected by SaveChanges(). If you want to add others, include in the Create-Action or try making a EF-transaction.
Without transaction the context is lost after the Create-Method ends
SITUATION:
I have a Model and based on a users Role I want to allow the user to only update certain parts of the model. Lets say the Model has three fields. (My Model is obviously more complex than this)
MyObject
Field1
Field2
Field3
My View looks something like this:
Html.TextBoxFor(#Model.Field1)
Html.TextBoxFor(#Model.Field2)
#if(UserIsAdmin())
Html.TextBoxFor(#Model.Field3)
else
#Model.Field3
Bearing with me on the syntax (and the poor design of the example), you can see what I'm trying to do. Upon the user posting the form my controller would just take the MyObject and save it back to the database, we are using EF.
QUESTION:
My question is, is there a way to stop a user from forging a POST to be able to save data he/she should not be able to. My current idea would be to do a check in the controller to see if the user modified values he should not have. Or I could save fields individually, but neither is a convient solution.
Is there a better one?
Thanks!
Additional Info:
Not sure if this artical is relevant at all: http://blog.stevensanderson.com/2008/09/01/prevent-cross-site-request-forgery-csrf-using-aspnet-mvcs-antiforgerytoken-helper/
All three fields are from the same database table and I'm using EF to get and save the entity.
You want to make sure the user is only able to update permitted fields.
You decided that the way to achieve this is to prevent the user "forging" a response using e.g. firebug, or F12 developer tools, or GreaseMonkey, and have asked how to do this.
But the correct/best method is to check which fields the user is attempting to update, and only update those which he is permitted to update. Then it doesn't matter if they forge the request or not, they still won't be able to access anything they shouldn't. In other words, check access rights at the point of access.
Anti-forgery tokens are there to solve a separate problem, namely XSRF.
Use a viewmodel that accepts only the fields that should be updated and then populate the model with those values. You could use something like AutoMapper for mapping between the two.
My current idea would be to do a check in the controller to see if the user modified values he should not have. Or I could save fields individually, but neither is a convient solution.
You're on the right track with that idea. A typical Add() operation would look like this:
public class FooController : Controller
{
public ActionResult Add(FooViewModel viewModel)
{
if (ModelState.IsValid)
{
FooDataModel dataModel = FooMapper.MapToDataModel(viewModel, User);
FooRepository.Add(dataModel);
}
}
}
Like #VimalStan said, your FooViewModel is then a model that contains only the fields you want to let the user update. Also this still doesn't solve your problem, which should be done in your mapper (in this case FooMapper) and still check every field as #Ben suggested:
public static class FooMapper
{
public static FooDataModel Map(FooViewModel viewModel, IPrincipal user)
{
var dataModel = new FooDataModel();
dataModel.Field1 = viewModel.Field1;
dataModel.Field2 = viewModel.Field2;
if (IsAllowedToUpdateField3(user))
{
dataModel.Field3 = viewModel.Field3;
}
return dataModel;
}
public static bool IsAllowedToUpdateField3(IPrincipal user)
{
return false; // your logic
}
}
After a pair programming session, an interesting question came up which I think I know the answer for.
Question: Is there any other desired way in ASP.NET MVC to retain 'state' other than writing to database or a text file?
I'm going to define state here to mean that we have a collection of person objects, we create a new one, and go to another page, and expect to see the newly created person. (so no Ajax)
My thoughts are we don't want any kung-fu ViewState or other mechanisms, this framework is about going back to a stateless web.
What about user session? There are plenty of valid use cases to store things in session. And what about a distributed caching system like memcached? You also seem to leave out the query string - which is an excellent state saver (?page=2). To me those seem like other desirable methods to save state across requests...?
My thoughts are we don't want any kung-fu ViewState or other mechanisms, this framework is about going back to a stateless web.
The example you provided is pretty easy to do without any sort of "view state kung fu" using capabilities that are already in MVC. "User adds a person and sees that on the next screen." Let me code up a simple PersonController that does exactly what you want:
public ActionResult Add()
{
return View(new Person());
}
[HttpPost]
public ActionResult Add(PersonViewModel myNewPersonViewModel)
{
//validate, user entered everything correctly
if(!ModelState.IsValid)
return View();
//map model to my database/entity/domain object
var myNewPerson = new Person()
{
FirstName = myNewPersonViewModel.FirstName,
LastName = myNewPersonViewModel.LastName
}
// 1. maintains person state, sends the user to the next view in the chain
// using same action
if(MyDataLayer.Save(myNewPerson))
{
var persons = MyDataLayer.GetPersons();
persons.Add(myNewPersion);
return View("PersonGrid", persons);
}
//2. pass along the unique id of person to a different action or controller
//yes, another database call, but probably not a big deal
if(MyDataLayer.Save(myNewPerson))
return RedirecToAction("PersonGrid", ...etc pass the int as route value);
return View("PersonSaveError", myNewPersonViewModel);
}
Now, what I'm sensing is that you want person on yet another page after PersonSaveSuccess or something else. In that case, you probably want to use TempData[""] which is a single serving session and only saves state from one request to another or manage the traditional Session[""] yourself somehow.
What is confusing to me is you're probably going to the db to get all your persons anyway. If you save a person it should be in your persons collection in the next call to your GetPersons(). If you're not using Ajax, than what state are you trying to persist?
ASP.NET MVC offers a cleaner way of working with session storage using model binding. You can write a custom model binder that can supply instances from session to your action methods. Look it up.
I'm trying to go through the NerdDinner example chapter from the ASP.Net MVC 1.0 and I've come across an error. Everything was hunky dory until I got to the part where I need to edit a dinner. I've followed the guide word for word from the creation of the project until this point (at least the best I can tell). However, when I call the SubmitChanges method on the NerdDinnerDataContext object I get an exception that says:
Rule violations prevent saving
I don't notice any differences between my code right now and the code that is in the final project (other than some additional functionality that I haven't added yet, obviously). Basically, I have no idea how to go about troubleshooting this error at this point. I've tried to look for some answers online, with no luck.
Here are some code snippets from my project, though I'm not sure how much good they will be.
from my DinnerRepository class:
private NerdDinnerDataContext db = new NerdDinnerDataContext();
...
public void Save()
{
db.SubmitChanges();
}
from the DinnersController
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues)
{
// Retrieve existing dinner
Dinner dinner = dinnerRepository.GetDinner(id);
// Update dinner with form posted values
dinner.Title = Request.Form["Title"];
dinner.Description = Request.Form["Description"];
dinner.EventDate = DateTime.Parse(Request.Form["EventDate"]);
dinner.Address = Request.Form["Address"];
dinner.Country = Request.Form["Country"];
dinner.ContactPhone = Request.Form["ContactPhone"];
// Persist changes back to database
dinnerRepository.Save();
// Perform HTTP redirect to details page for the saved Dinner
return RedirectToAction("Details", new { id = dinner.DinnerID });
}
How can I go about troubleshooting this issue? How can I find what these "rule violations" are?
This is my first SO question, so my apologies if it isn't that great.
RuleViolations is how Scott Hanselman, the creator of NerdDinners, decided to encapsulate business logic.
He partialed out the Linq To SQL classes and added a function named GetRuleViolations(), which is where he added all of his business rules. Take a look at that method to see what's going on.
I had the same problem like atcrawford, but thanx to Giovanni i managed to resolve it.
First, when i began this tutorial i populated the phone number from the database with some random numbers.
Now when i tried to edit my existing data i received this "rule violations" because the phone number hadn't the correct form.
So look in Models folder at Dinner.cs at:
public IEnumerable<RuleViolation> GetRuleViolations()
{//if (!PhoneValidator.IsValidNumber(ContactPhone, Country))
// yield return new RuleViolation("Phone# does not match country", "ContactPhone");
}
You can see that i commented that line, so the IsValidNumber method on PhoneValidator class is never called.
Or, you can enter the data for the phone number according to the regular expressions from PhoneValidator
For more information on the set up of nerd dinner, be sure to look at the ASP.NET MVC 1.0 book and the free first chapter.
There is a link to it on website you mentioned. Here it is again, http://tinyurl.com/aspnetmvc