When you generate grails views, grails looks at your relationships and generates the right html for your form data to be automatically binded to the back end domain. For one to one associations grails creates a drop down list.
However, you might not want to present that property as a drop down list but something more custom (for example a text field with autocomplete). As soon as you do that the value that comes to the controller from that field, comes in as a String and you have to first:
Clear errors
Perform a findBy based on a given param and assign it to the property of the domain
I really want to avoid doing findBys in the controller as much as possible because it seems like I am doing logic/things that should not go there. The controller should delegate to the Service layer. It is not clear to me from the grails documentation how would I do that by using bindData which seems to work really well with String, date, Integer properties etc.. but I do not see how bindData is used for properties that are other domains.
I also really want to avoid passing the params object to the Service layer as it seems less reusable (or maybe not, correct me if I am wrong). I guess that I do not like how it looks semantically. I would prefer the first over the second:
#Transactional
class WithdrawService {
def addWithdraw(Withdraw withdraw) {
//perform business logic here
}
def createWithdraw(Map params){
//perform business logic here
}
}
Let's take the following example:
class Withdraw {
Person person
Date withdrawDate
}
and the parent lookup table
class Person {
String name
String lastName
static constraints = {
}
#Override
public String toString() {
return "$name $lastName"
}
}
In order for the bind to happen automatically without any extra work grails passes in the following request params to automatically bind the one to one:
person.id
a person map with the id.
[person.id:2, person:[id:2], withdrawDate:date.struct, withdrawDate_month:11, create:Create, withdrawDate_year:2015, withdrawDate_day:10, action:save, format:null, controller:withdraw]
What is the best way to go about this?
Pass two hidden fields that look exactly like this: person.id:2, person:[id:2] that get populated as a result of the Ajax call that populates the autocomplete?
In the controller do a Person.findBySomeKnownProperty(params.someKnownValue)
Or any other approach?
Related
Just got to know about the capability of createCriteria() method. Just wanna know that other than applying it on the Controller, is there a way to apply onto the domain classes as well? Probably on its own mapping to a property like:
static mapping = {
additionalInfo: Page.createCriteria().list()
}
Perhaps you may want to simply create a new field based the target field like the below example:
class myInfo {
String additionalInfo
String[] moreInfo // a transient field
getMoreInfo(){
def myresultmap = createCriteria.list{
// insert any other criteria shenanigans
}
return myresultmap
}
static transients = ['moreInfo']
}
In a controller return a view like normal with the Domain instance of class MyInfo
Then use in view like:
<g:each in="${domaininstancefromcontroller}">
${it.moreInfo[0]
</g:each>
see docs.
Hope this helps.
Just wanna know that other than applying it on the Controller, is
there a way to apply onto the domain classes as well?
Criteria queries are not limited to controllers, you can apply them elsewhere using the same syntax that you would in a controller. However, the particular example you show there is probably going to be problematic because you are trying to use GORM inside of the mapping block which is used to configure GORM.
I have the following model:-
[MetadataType(typeof(TMSServer_Validation))]
[Bind(Exclude = "TMSRack,ServerModel")]
public partial class TMSServer
{
}
and I have the following drop down inside my view:-
#Html.DropDownListFor(model =>model.Server.TMSRack.DataCenterID, ((IEnumerable<TMS.Models.DataCenter>)ViewBag.DataCenters).Select(option => new SelectListItem {
Text = (option == null ? "None" : option.Name),
Value = option.ID.ToString(),
Selected = (Model.Server.TMSRack != null) && (option.ID == Model.Server.TMSRack.DataCenterID)
}), "Choose...")
Then on my controller class I have the following :-
ViewBag.Racks = repository.getrelatedracks(Server.TMSRack.DataCenterID);
But since I have excluded the TMSRack navigation property (mainly to avoid over-posting attacks), so the Server.TMSRack.DataCenterID will always be null. And to get its value I wrote the following:-
ViewBag.Racks = repository.getrelatedracks(Int32.Parse( Request.Form["Server.TMSRack.DataCenterID"]));
But I know that using Request.Form is not the right approach to follow, so my question is there a way to get the excluded property using more reliable way ?
Thanks
My answer is going to assume TMSServer is a domain model.
With that in mind, this is the perfect example of when to use a view model. By using a view model instead, you have complete control over how the properties are mapped from the view model to the domain model. Something like:
public class RackViewModel
{
public int DataCenterID
// other Rack properties
}
Then either send a list of RackViewModel to your view, or create a view model that encompasses all of that, too:
public class ContainerViewModel
{
public List<RackViewModel> Racks { get; set; }
// other view-specific properties
}
Now, when you POST the data back, not only do you have complete control over what properties you want to bind to your view models, you also have complete control over the mapping that takes place from converting your view models to domain models.
The bottom-line is this: if your view accepts a view model that only allows the user to POST the data they should be allowed to POST, over-posting doesn't even exist. Well-designed view models, or even making the distinction between a view model and an input model (i.e. a separate model that represents the data you want to bind back to in your action), eliminates over-posting entirely.
Over-posting only exists because you're not restricting the model binding process enough. If you ask it to bind to a class that has 10 properties in it when you only need 3 you're allowing the user to potentially stuff data into those other 7 properties.
This is one reason why view models are so popular. They allow you to narrow the scope of your view, whilst also narrowing the scope of the model binder. That leaves you free to properly manage the process of mapping from your view model to your domain model, without introducing a vulnerability.
Update
As you don't want to go the view model approach, your idea will work but you can do it slightly differently. Something along the lines of:
public ActionResult SomeAction(SomeModel model, TMSRack rack)
Where:
SomeModel is the type of model you're decorating with Bind(Exclude...) (it's not obvious what type that is from your question.
TMSRack is the type I assume you want to bind to.
As TMSRack is defined in your main model anyway, as long as you're using the Html.* helpers, it will have the correct names generated for it on the form in order to bind straight back to it as a separate parameter on your action. Then you can do whatever you want with it, without resorting to Request.Form.
I have two models: Reptile and Species. A Reptile has a Species, stored as an ID in the database:
How should I set up the details controller action/view for Reptile so that it displays the Title property of the Species instead of the ID that the Reptile uses?
My initial thought was just to grab the data in the controller and pass it in the ViewBag, but this seems inappropriate, and overly complex when it's time to setup the list action.
What's the proper way to do this?
It seems like I need to make a view model, but what confuses me is how to properly design it so that there aren't too many database calls.
Here is my initial attempt at a ViewModel:
public class ReptileDetailsModel
{
[Required]
public String Species { get; set; }
//etc...
public ReptileDetailsModel(Reptile reptile)
{
this.Species = reptile.Species.Title;
// etc...
}
}
Another way to achieve the same thing in more generic way is to use AutoMapper
Few advantages I can think of:
Automatically map exact properties (you only need specify anything that is exception to the rule)
Centralized in one class / method, whatever
Ability to ignore, map to another classes properties, even custom logic
Non intrusive, it is up to you how / when you want to use it.
In your particular instance I would create a mapper something like
Mapper.CreateMap<Reptile, ReptileDetailsModel>()
.ForMember(dest => dest.Species,
options => options.MapFrom(source => source.Species.Title));
This mapper info need to be registered somewhere. In MVC projects I have been involved, I would register a mapper into global.asax.
Then in your controller, you would want to invoke the mapper to map your reptile instance to your model
ReptileDetailsModel model = Mapper.Map<ReptileDetailsModel>(reptile);
There are many ways to use the AutoMapper within MVC, but this is probably a start.
I didn't realize it at the time, but I was using:
public ActionResult Index()
{
using (var db = new ModelsContainer())
{
return View(db.Reptiles.ToList());
}
}
This was causing the database (and thus model property) to expire before the view was rendered, causing this error (adding for search engines):
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
Set the Species class as a model for your strongly typed Reptile View. Then display the Title property of it. Your action method should look like this:
public ActionResult Reptile(Reptile rep)
{
return View(db.Species.Where(x=>x.ID == rep.SpeciesID).Single());
}
this way you would only need to call database once in order to generate the view.
I have a Note domain object which belongs to a Document object. Only an owner of a Document can add Notes so in the Document class there is a canUserAccess() method. In my service layer I can call canUserAccess() to ensure a user only adds Notes to Documents they own.
This works well for create but I have hit a problem with my Note edit action. On post, the viewmodel is mapped to a Note object, providing it with a DocumentID. Problem is, a malicious user could send in a DocumentID on which they do have permission and thus edit a Note belonging to a Document they don't. In my service layer I cannot reliably use the supplied DocumentID yet I need to get the related DocumentID in order to verify that the user can edit the Note. This is an example:
public void editNote(Note note)
{
note.Document = documentRepository.Find(note.DocumentID);
if(note.Document.canUserAccess())
}
How do I get around this? It seems I need to avoid passing the DocumentID with the edit viewmodel but how do I hydrate the related Document in the service layer? There is probably a really simple solution to this and I am just tying myself up in circles!
You do this with BindAtrribute for the model or for the action method by adding a white list with the properties you want to be bound :
for the model
[Bind(Include="NoteText,NoteTitle")]
public Model{}
for the action method
public ViewResult Edit([Bind(Include="NoteText,NoteTitle"]Note note)){}
or use a black list for the properties you don't want to bind :
[Bind(Exclude="DocumentID")]
public Model{}
I would personally use white list with the model class. You might find this article interesting. The last section for under-posting is your case.
Then you don't have the documentID passed, but in your action you can do this:
public ViewResult Edit(Note note)
{
Note updateNote = nodesRep.Get(note.noteID);
if(updateNote.Document.canUserAccess())
{
//replace the values of the updateNote
//you can place this line in your base Repository class
context.Entry<Note>(updateNote).CurrentValues.SetValues(note); //I assume EF 4.1
}
}
I'm working on an ASP.NET MVC project that will allow users to perform batch edits on the attributes of objects. The implementation is in a sort of "wizard" like form with four phases to the process as follows:
"Select the attributes you want to edit" - the first page will present the user with a list of checkboxes representing each of the attributes they want to edit. The user should check the attributes they wish to edit and select "Continue".
"Edit the selected attributes" - the second page will present the user with a list of distinct "editors" which will be unique for each of the attributes they selected on the first page.
"Review your changes" - this page will allow the user to review the changes they've made to the attributes they selected.
"Submit your changes" - this page will actually submit the information about the edits the user wishes to make to the selected attributes against the selected collection of objects.
Fairly straight-forward.
As I mentioned, the "editor" will be unique to each attribute, and could have any combination of different controls on it. Once a user has made their edits and the application posts that information to the "Review" page is where I'm currently having my problem.
We've developed the concept of an "EditorWorker" class that is unique to each attribute, which is responsible for generating the ViewModel necessary for each editor, but is also responsible for creating/returning (within the "Review" page controller action) an object that is the "model" object for the editor that the post data can be bound to, which can then be use to display the edited data for review. This object should have properties that match up with the IDs of the controls in the editor so that model binding can occur.
I've got the "EditorWorker" creating and returning the class needed, but for some reason, when I call TryUpdateModel and pass in that class, its properties aren't getting populated as a result of that method call as I would expect them to. I have verified that the values are in the posted FormCollection. Below is the code for my controller action where I'm attempting to do this. If someone can help me understand why TryUpdateModel isn't working in this scenario, I would be very appreciative.
[HttpPost]
public virtual ActionResult Review(ReviewBatchViewModel model)
{
var selectedAttributes = GetSelectedAttributes(model.SelectedAttributeIds.Split(',').Select(i => Int64.Parse(i)).ToArray());
var workers = new List<IEditorWorker>();
var reviewData = new Dictionary<ViewAttribute, IEditData>();
foreach (var attribute in selectedAttributes)
{
if (!string.IsNullOrEmpty(attribute.EditorWorker)) // If there is no EditorWorker defined for this object, move on...
{
var worker = ServiceLocator.Current.GetInstance(Type.GetType(string.Format("{0}.{1}", EditorWorkerNamespace, attribute.EditorWorker)));
var attributeEditData = ((IEditorWorker)worker).LoadEditData();
if (TryUpdateModel(attributeEditData))
model.EditData.Add(attributeEditData); // model.EditData is a List<IEditData> that will be iterated on the Review page
reviewData.Add(attribute, attributeEditData);
}
}
return View(model);
}
// ReviewBatchViewModel.cs
public class ReviewBatchViewModel : BaseViewModel
{
public ReviewBatchViewModel() { EditData = new List<IEditData>(); }
public string SelectedAttributeIds { get; set; }
public List<ViewAttribute> SelectedAttributes { get; set; }
public List<IEditData> EditData { get; set; }
}
// IEditData.cs
public interface IEditData
{
}
// BroadcastStatusEditData.cs
public class BroadcastStatusEditData : IEditData
{
public int BroadcastStatus { get; set; }
}
I totally understand that this controller action is incomplete in its current state. I'm presently working on just trying to get those EditData objects populated correctly before I move on. As mentioned, any thoughts would be greatly appreciated. Thanks.
UPDATE: With regards to #mare's comment, I should have explained that part more clearly, sorry. The call to TryUpdateModel actually is returning true, but the fields on the model object being passed into it aren't actually being populated from the values that have been confirmed present in the posted form data. The model object being passed into the call is not a List, its just a poco. The resulting, ultimately hopefully populated model object is then being added to a List collection of model objects that will then be used for displaying the posted data for review on the Review page. I'm not loading anything from a datastore at all. Unique editors for each selected attribute are being rendered to the Edit screen, and I'm attempting to capture the edit values for display on a Review screen prior to submitting the batch of edits to a service. Hopefully that's more clear. Thanks.
UPDATE 2: I've included the definition of the ReviewBatchViewModel class as requested by #mare in the comments. The use of the var keyword in most cases in this code sample is largely due to the fact that the methods that are populating those variables is going to be returning an object of a different type for each attribute selected, so I never know exactly what its going to be at runtime (although it will always implement an interface, in this case either IEditorWorker and/or IEditData). There is a single class in the Model called "Attribute". The provided code sample has three variables relative that class: 1) SelectedAttributeIds is a comma-separated list of the Id's of the attributes that the user has selected to edit, which gets passed from the Edit page to the Review page via hidden field, 2) selectedAttributes is a collection of the actual Attribute objects that correspond to those Ids that I can work with, and 3) attributeEditData is an instance of the IEditData class specific to each given attribute that I'm attempting to bind the posted data from the Edit page to.
Hopefully this additional information clears things up even more.
TryUpdateModel is a generic method, and therefore attempts to infer all type information based on the Generic Type Parameter.
From what I understand in your example above, you are always passing in a IEditData correct?
In effect you are saying:
TryUpdateModel<IEditData>(attributeEditData)
This is most likely the cause for not seeing any properties being set, since IEditData doesn't have any properties ;)
To do what you want you will probably have to create a custom ModelBinder.
As a quick code review side note, your solution seems overly complicated. I had to stare at your solution for a good while just to figure out where to start. Creating a custom model binder may solve your immediate problem, but you might be looking at a big time maintenance headache here. I'm willing to bet there is a simpler approach that will lead to fewer problems down the road.
Based on your comments I have changed the code around from System.Object to your IEditData interface, but everything still holds. I noticed in an earlier comment you mentioned using var because you didn't know the type until runtime. However, there is nothing magic about the var keyword. The only thing it does is give you implicit typing, but it is still statically typed.
The nice thing about MVC is that you can just pop over to Codeplex and have a look at the source for TryUpdateModel if you want. Digging down a few layers you will eventually find a call to this internal method:
protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IDictionary<string, ValueProviderResult> valueProvider) where TModel : class {
if (model == null) {
throw new ArgumentNullException("model");
}
//valueProvider is passed into this internal method by
// referencing the public ControlerBase.ValueProvider property
if (valueProvider == null) {
throw new ArgumentNullException("valueProvider");
}
Predicate<string> propertyFilter = propertyName => BindAttribute.IsPropertyAllowed(propertyName, includeProperties, excludeProperties);
//Binders is an internal property that can be replaced by
// referencing the static class ModelBinders.Binders
IModelBinder binder = Binders.GetBinder(typeof(TModel));
ModelBindingContext bindingContext = new ModelBindingContext() {
Model = model,
ModelName = prefix,
ModelState = ModelState,
ModelType = typeof(TModel),
PropertyFilter = propertyFilter,
ValueProvider = valueProvider
};
binder.BindModel(ControllerContext, bindingContext);
return ModelState.IsValid;
}
Notice the use of typeof(TModel) everywhere... in your case that is getting translated into typeof(IEditData), which isn't very useful since it is only a marker interface. You should be able to adapt this code for your own use, making sure to use GetType() in order to get the actual type at runtime.
I hope this helps out!
P.S. I've added some comments to the above code to help out a little
#Josh, you were very helpful in helping me understand why TryUpdateModel wasn't working for me, and I appreciate that. Unfortunately, I think the larger issue here was that fact that I (not exactly sure which) was either unable or unwilling to try to document all of the details of the requirements for the problem I'm trying to solve here, which I think made it difficult for anyone to be able to provide much meaningful input. The biggest problem for us is that, because we have no idea until runtime which attributes a user has selected for editing, we don't know which objects we'll be working with in the context of these controller actions, or what their types will be. The one place that we safely can work with known data and types, is within the context of each of the unique EditorWorker objects, which is where I've chosen to do the heavy lifting here.
I was hoping and attempting to take advantage of all of the heavy lifting that MSFT has done for us within the MVC framework to handle model binding, but I've come to the conclusion at this point that I don't think that's going to work for us. The solution that I've come up with at this point, is to allow the LoadEditData method of the EditorWorker classes handle loading up the EditData classes for for me. As each EditorWorker class is unique to, and has knowledge of the attribute that it is associated with. The problem I was having originally was that I was letting the EditorWorker.LoadEditData method just return an empty instance of the specific type of EditData class that I needed for the attribute I was currently working with, and let the MVC framework handle model binding to that object for me. That wasn't working because that method is designed to return an object of type IEditData, and I never really knew exactly what type it was that I was currently working with, so I had no way of specifying the type in the call to either of the typed methods: TryUpdateModel<T> or UpdateModel<T>.
So the solution I've come up with, and am going with at least for now (re-education and/or refactoring may very well change this in the future, who knows) is to just pass the Request.Form object into the call to EditorWorker.LoadEditData and let that method handle actually loading up the EditData object that it knows it needs to return for the attribute it's responsible for, which it can do as it knows what information should be in the posted form collection to load up its EditData object.
So that's where I'm at for now. Thanks for the help.