So, I'm attempting to rewrite an old asp.net app and thought I'd do it in in MVP this time.
The app displays a dynamically generated form by placing labels and input fields in a table.
What I get from my Model is a list of entities that describe what control's should be rendered.
Now this list needs to be converted into a table with a lable and control on each row, but I can't decide where and how to do this.
These are the scenario's I could think of, but I have no idea which one is right according to MVP:
For each item create a table row in the presenter and call View.AddRow(row)
Create a list of table rows in the presenter and call View.AddRows(list)
For each item call View.CreateRow(info)
Any ideas?
Thanks!
The key of the MVP pattern is to separate the concerns between the view and the presenter. The presenter only has to set the list of entities and whether the data is presented as a table using a GridView, Repeater etc is the concern of the view.
If I were doing what you would describe I would use a 'view model' class to act as a wrapper for creating the controls:
public class DynamicControlViewModel
{
public enum ControlTypes
{
TextBox,
DropDown,
CheckBox
}
public string LabelValue { get; set; }
public ContrlTypes ControlType { get; set; }
}
My View would look as follows:
interface IDynamicControlsView
{
IEnumerable<DynamicControlViewModel> DynamicControls { set; }
}
And the presenter:
// This method would be called within your Presenter
public override void Initialize()
{
_view.DynamicControls = ConvertDataToViewModel(data); // your method to fetch the data would replace 'data'
}
The property setter on the view implementation (the .ASPX code behind) would look as:
public IEnumerable<DynamicControlViewModel> DynamicControls
{
set
{
foreach (DynamicControlViewModel model in value)
{
// build up user controls here....
switch (model.ControlType)
{
case DynamicControlViewModel.ContrlTypes.TextBox:
// add text box
break;
case DynamicControlViewModel.ContrlTypes.DropDown:
// add drop down
break;
case DynamicControlViewModel.ContrlTypes.CheckBox:
// add checkbox
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
Using the view model would make unit testing easier and keep a cleaner separation of concerns.
Related
I'm trying to better understand how to properly structure my ASP.NET MVC code to handle a situation where a single view contains multiple forms. I feel that it makes sense to submit the forms to their own action methods, so that each form can benefit from its own view model parameter binding and validation, and to avoid putting all form parameters into 1 larger, monolithic view model.
I'm trying to code this pattern, but I can't seem to tie the loose ends together.
I've written some example action methods below, along with example view model classes, that I think demonstrate what I'm trying to achieve. Lets say that I've got an Item Detail action method and view. On this Detail view, I've got two forms - one that creates a new Comment and another that creates a new Note. Both Comment and Note forms POST to their own action methods - DetailNewComment and DetailNewNote.
On success, these POST handler action methods work just fine. On an invalid model state though, I return View(model) so that I can display the issues on the original Detail view. This tries to render a view named Brief though, instead of Detail. If I use the overloaded View call that allows me to specify which view to render, then now I have issues with the different view model classes that I'm using. The specific view model classes now no longer work with the original DetailViewModel.
I get the feeling that I'm doing this completely wrong. How am I supposed to be handling this scenario with multiple forms? Thanks!
public ActionResult Detail(int id)
{
var model = new ItemDetailViewModel
{
Item = ItemRepository.Get(id)
};
return View(model);
}
[HttpPost]
public ActionResult DetailNewComment(int id, ItemDetailNewCommentViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var comment = CommentRepository.Insert(new Comment
{
Text = model.Text
});
return RedirecToAction("Detail", new { id = id; });
}
[HttpPost]
public ActionResult DetailNewNote(int id, ItemDetailNewNoteViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var note = NoteRepository.Insert(new Note
{
Text = model.Text
});
return RedirectToAction("Detail", new { id = id; });
}
... with view models something like ...
public class ItemDetailViewModel
{
public Item Item { get; set; }
}
public class ItemDetailNewCommentViewModel
{
public string Text { get; set; }
}
public class ItemDetailNewNoteViewModel
{
public string Text { get; set; }
}
For your case I'd recommend to have a master model for example your
ItemDetailViewModel class to which you'll add a property for each sub-model
public class ItemDetailViewModel
{
public Item Item { get; set; }
public ItemDetailNewCommentViewModel NewCommentModel {get;set;}
public ItemDetailNewNoteViewModel NoteModel {get;set;}
}
Your Detail view will be the master view and the other two will be partial views.
Master view will receive an instance of ItemDetailViewModel as model and inside view you will render your partials by passing Model.NewCommentModel and Model.NoteModel as their corresponding models. For being able to use separate actions for each form, instead of regular forms you can use ajax forms, thus you will send to the server only relevant information without altering the rest of the master view.
The chief problem here is what happens when the user messes up and their post doesn't pass validation server-side. If you choose to take them to a page where just the one form is presented, then you can post to a different action, but if you want both forms re-displayed, then they both should point to the same action.
Really, you just have to make a choice. I've seen sites handle it both ways. Personally, I prefer to re-display the original form, which means handling both forms in the same action. It can lead to bloat, but you can factor out a lot of logic from the action such that you end up with mostly just a branch depending on which form was submitted.
I have a custom control for building forms to automatically generate a label-input field pair. But I need to be able to modify attributes of the generated label-input field pair in my vb code behind file. The problem is that the code behind file seems to be applied before the custom control. How do I work around this issue?
You could expose the inner controls as public properties of your custom control.
For example:
public class MyCustomControl : CustomControl
{
Label _label;
// initialize _label in OnInit() ...
public string LabelText { get { return _label.Text; } set { _label.Text = value; } }
}
In my ASP.NET MVC 2 web application, I allow users to create custom input fields of different data types to extend our basic input form. While tricky, building the input form from a collection of custom fields is straight-forward enough.
However, I'm now to the point where I want to handle the posting of this form and I'm not certain what the best way to handle this would be. Normally, we'd use strongly-typed input models that get bound from the various statically-typed inputs available on the form. However, I'm at a loss for how to do this with a variable number of input fields that represent different data types.
A representative input form might look something like:
My date field: [ date time input
control ]
My text field: [ text input
field ]
My file field: [ file upload
control ]
My number field: [ numerical input control ]
My text field 2: [text input field ]
etc...
Ideas I've thought about are:
Sending everything as strings (except for the file inputs, which would need to be handled specially).
Using a model with an "object" property and attempting to bind to that (if this is even possible).
Sending a json request to my controller with the data encoded properly and attempting to parse that.
Manually processing the form collection in my controller post action - certainly an option, but I'd love to avoid this.
Has anyone tackled an issue like this before? If so, how did you solve it?
Update:
My "base" form is handled on another input area all together, so a solution doesn't need to account for any sort of inheritence magic for this. I'm just interested in handling the custom fields on this interface, not my "base" ones.
Update 2:
Thank you to ARM and smartcaveman; both of you provided good guidance for how this could be done. I will update this question with my final solution once its been implemented.
This is how I would begin to approach the issue. A custom model binder would be pretty easy to build based on the FormKey property (which could be determined by the index and/or label, depending).
public class CustomFormModel
{
public string FormId { get; set; }
public string Label { get; set; }
public CustomFieldModel[] Fields { get; set; }
}
public class CustomFieldModel
{
public DataType DateType { get; set; } // System.ComponentModel.DataAnnotations
public string FormKey { get; set; }
public string Label { get; set; }
public object Value { get; set; }
}
public class CustomFieldModel<T> : CustomFieldModel
{
public new T Value { get; set; }
}
Also, I noticed one of the comments below had a filtered model binder system. Jimmy Bogard from Automapper made a really helpful post about this method at http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/03/17/a-better-model-binder.aspx , and later revised in, http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/11/19/a-better-model-binder-addendum.aspx . It has been very helpful for me in building custom model binders.
Update
I realized that I misinterpreted the question, and that he was specifically asking how to handle posting of the form "with a variable number of input fields that represent different data types". I think the best way to do this is to use a structure similar to above but leverage the Composite Pattern. Basically, you will need to create an interface like IFormComponent and implement it for each datatype that would be represented. I wrote and commented an example interface to help explain how this would be accomplished:
public interface IFormComponent
{
// the id on the html form field. In the case of a composite Id, that doesn't have a corresponding
// field you should still use something consistent, since it will be helpful for model binding
// (For example, a CompositeDateField appearing as the third field in the form should have an id
// something like "frmId_3_date" and its child fields would be "frmId_3_date_day", "frmId_3_date_month",
// and "frmId_3_date_year".
string FieldId { get; }
// the human readable field label
string Label { get; }
// some functionality may require knowledge of the
// Parent component. For example, a DayField with a value of "30"
// would need to ask its Parent, a CompositeDateField
// for its MonthField's value in order to validate
// that the month is not "February"
IFormComponent Parent { get; }
// Gets any child components or null if the
// component is a leaf component (has no children).
IList<IFormComponent> GetChildren();
// For leaf components, this method should accept the AttemptedValue from the value provider
// during Model Binding, and create the appropriate value.
// For composites, the input should be delimited in someway, and this method should parse the
// string to create the child components.
void BindTo(string value);
// This method should parse the Children or Underlying value to the
// default used by your business models. (e.g. a CompositeDateField would
// return a DateTime. You can get type safety by creating a FormComponent<TValue>
// which would help to avoid issues in binding.
object GetValue();
// This method would render the field to the http response stream.
// This makes it easy to render the forms simply by looping through
// the array. Implementations could extend this for using an injected
// formatting
void Render(TextWriter writer);
}
I am assuming that the custom forms can be accessed via some sort of id which can be contained as a form parameter. With that assumption, the model binder and provider could look something like this.
public interface IForm : IFormComponent
{
Guid FormId { get; }
void Add(IFormComponent component);
}
public interface IFormRepository
{
IForm GetForm(Guid id);
}
public class CustomFormModelBinder : IModelBinder
{
private readonly IFormRepository _repository;
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult result;
if(bindingContext.ValueProvider.TryGetValue("_customFormId", out result))
{
var form = _repository.GetForm(new Guid(result.AttemptedValue));
var fields = form.GetChildren();
// loop through the fields and bind their values
return form;
}
throw new Exception("Form ID not found.");
}
}
Obviously, all the code here is just to get the point across, and would need to be completed and cleaned up for actual use. Also, even if completed this would only bind to an implementation of the IForm interface, not a strongly typed business object. (It wouldn't be a huge step to convert it to a dictionary and build a strongly typed proxy using the Castle DictionaryAdapter, but since your users are dynamically creating the forms on the site, there is probably no strongly typed model in your solution and this is irrelevant). Hope this helps more.
Take a peek at what I did here: MVC2 Action to handle multiple models and see if can get you on the right track.
If you use a FormCollection as one of your parameters to your action, you can then go thru that form collection looking for bits of data here or there in order to bind those values to whatever an then save the data. You are most likely going to need to take advantage of both strategy and command patterns to get this to work.
Best of luck, feel free to ask follow-up questions.
Edit:
Your method which does the work should look something like this:
private/public void SaveCustomFields(var formId, FormCollection collection) //var as I don't know what type you are using to Id the form.
{
var binders = this.binders.select(b => b.CanHandle(collection)); //I used IOC to get my list of IBinder objects
// Method 1:
binders.ForEach(b => b.Save(formId, collection)); //This is the execution implementation.
// Method 2:
var commands = binders.Select(b => b.Command(formId, collection));
commands.ForEach(c => c.Execute());
}
public DateBinder : IBinder //Example binder
{
public bool CanHandle(FormCollection collection)
{
return (null != collection["MyDateField"]); //Whatever the name of this field is.
}
//Method 1
public void Save(var formId, FormCollection collection)
{
var value = DateTime.Parse(collection["MyDateField"]);
this.someLogic.Save(formId, value); //Save the value with the formId, or however you wish to save it.
}
//Method 2
public Command Command(var formId, FormCollection collection)
{
//I haven't done command pattern before so I'm not sure exactly what to do here.
//Sorry that I can't help further than that.
}
}
I would think one of the best options is to create a custom model binder, which makes it possible to have custom logic behind the scenes and still very customizable code behind.
Maybe these articles can help you:
http://www.gregshackles.com/2010/03/templated-helpers-and-custom-model-binders-in-asp-net-mvc-2/
http://www.singingeels.com/Articles/Model_Binders_in_ASPNET_MVC.aspx
More specifically I would probably take as the controller argument a custom class with all "base" properties included. The class could then for example include a dictionary linking the name of each field to either just an object or an interface which you implement once for each data-type making it simple to process the data later.
/Victor
I'm using Model-View-Presenter framework. When Loading a page, I'm having trouble setting the selected item that came from the Database.
In view, I know I need:
protected void ddlStatus_SelectedIndexChanged(object sender, EventArgs e)
{
presenter.DdlStatusSelectedIndexChanged();
// what should this pass?
}
Then in Presenter:
public void DdlStatusSelectedIndexChanged()
{
view.DdlStatus = ???
// Should I pass the SelectedIndex?
}
I also think that part of my problem is that DdlStatus I have as a List.
Interface:
List<StatusDTO> DdlStatus { set; get; }
Does anybody have some simple examples of this?
The best I found is here (but needs formatted!) --->
http://codebetter.com/blogs/jeremy.miller/archive/2006/02/01/137457.aspx
Thanks!
Which framework are you using? The typical way the presenter/view relationship works is through events; the view defines events that the presenter attaches to, to receive those state change notifications. There are other options too.
Your model should contain the list of statuses and the selected status. Depending on the "flavor" of MVP, you would either have the presenter call a property on the view to pass it the selected index, and your view would pass it to the control, or the view takes the index from the model directly.
HTH.
I figured this out. It's a bit of a cheese but ...
public int DdlStatusSelectedIndex
{
set
{
for (int i = 0; i < ddlStatus.Items.Count; i++)
{
if (ddlStatus.Items[i].Value.Equals(value.ToString()))
{
ddlStatus.SelectedIndex = value;
}
}
}
}
We have developed a number of ASP.Net server controls and we need to test them. I want to instantiate a control, set some properties, call CreateChildControls and test the control-hierarchy.
I run into a number of problems:
The controls rely on HttpContext
CreateChildControls is private
Even adding a single child control to the controls collection calls the ResolveAdapter() method which relies on HttpContext.
How can I get around this?
p.s. I do not wish to test the controls on a page (!).
It sounds a lot like you don't care about the actual rendering of the control at all, but rather the logic contained within the control. For that I would suggest that you have another problem besides the inability to test the control outside the HttpContext.
If the logic only pertains to the control, then you should trust the framework to do it's job, and drop the control on a page to see if it works properly. If the logic you are attempting to test is business logic, then you need to refactor.
Pull out the business logic into a seperate Project/Dll somewhere, and think about implementing a MVP pattern with your server control. You don't have to go with a big heavy framework like WCSF either. Conceptually you can implement this with little effort.
Create an interface that represents the values on your view:
public interface IOrderView
{
Int32 ID{get; set;}
String Name{get; set;}
List<Item> Items {set;}
}
Once this is defined, you need a presenter that exercises this view:
public class OrderPresenter
{
public IOrderView View {get; set;}
public void InitializeView()
{
//Stuff that only happens when the page loads the first time
//This is only for an example:
var order = Orders.GetOrder(custId);
View.ID = order.ID;
View.Name = order.Name;
View.Items = order.Items;
}
public void LoadView()
{
//Stuff that happens every page load
}
}
Now your server control can implement this interface, and initialize itself with the OrderPresenter
public class OrderControl: Panel, IOrderView
{
private OrderPresenter Presenter{get; set;}
public OrderControl()
{
//Create new presenter and initialize View with reference
// to ourselves
Presenter = new OrderPresenter{View = this;}
}
protected override void OnLoad(EventArgs e)
{
if(Page.IsPostback)
{
_presenter.InitializeView();
}
_presenter.LoadView();
//Other normal onload stuff here...
}
//Now for the interface stuff
public Int32 ID
{
get{ return Int32.Parse(lblOrderId.Text); }
set{ lblOrderId.Text = value.ToString(); }
}
public String Name
{
get{ return lblOrderName.Text; }
set{ lblOrderName.Text = value; }
}
public List<Item> Items
{
set
{
gvItems.DataSource = value;
gvItems.DataBind();
}
}
}
And there you have it! You should be able to write unit tests against the OrderPresenter now using a stubbed out View. No HttpContext required, and you have cleaner seperation of concerns.
If you already have all your business logic seperated out then I appologize, but I can't think of any other reason to test a server control outside the ASP.Net runtime besides needing to verify actual business logic. If this is the case, then I would highly encourage you to refactor now before you realize the maintenance nightmare this will eventually cause via Leaky Abstractions.