A record is uniquely identified by its Kind
and RecordId
.
I have a set of records that I want to display as a dropdown, and on submitting I'd like to get SelectedKind
and SelectedRecordId
for further processing.
The pro开发者_开发知识库blem is, I want these values to be processed separately, but there should be only one dropdown.
What is the best practice for doing this?My suggestions:
Option 1
Populate the dropdown with strings like "Kind_RecordId"
and set up a JavaScript change
handler which will parse the string and set hidden field values.
However I don't quite like the idea of having string operation logic in different places (in controller when populating the dropdown, and in view when parsing the values). Should I move SelectItem
populating logic into view as well, and keep my business objects in the view model instead?
It also bugs me that in rare case when JavaScript is disabled, the user will not be able to sumbit the form at all.
Option 2
Expose a unique "Kind_RecordId"
-like string property in my form model, and let the model parse it (or fill it) through strong-typed accessor properties for the controller?
This seems like a better way but I'm not entirely sure. What I consider good about it is it'll keep all parsing/concatenating logic in view model, and it will not fail when JavaScript is not available.
Community thoughts are very appreciated.
An elegant way to do this is to create your own model binder and a separate class to hold Kind
and RecordId
as properties. You'll need to specify model binder for your class with an attribute, and you'll then be able to use the class as an action method parameter.
[ModelBinder (typeof (RecordIdentityBinder))]
public class RecordIdentity
{
// Your types may be different
public RecordKind Kind { get; set; }
public int RecordId { get; set; }
//TODO: replace this with your formatting algorithm
public override string ToString ()
{
return string.Format ("{0}_{1}", Kind, RecordId);
}
//TODO: replace this with your parsing algorithm
public static RecordIdentity Parse (string s)
{
string[] fragments = s.Split('_');
if (fragments.Length != 2)
return null;
// Your object creation may be different
return new MessagePartyIdentity {
Kind = (RecordKind) Enum.Parse (typeof (RecordKind), fragments [0]),
PartyID = int.Parse (fragments [1])
};
}
}
public class RecordIdentityBinder : IModelBinder
{
public object BindModel (ControllerContext controllerContext, ModelBindingContext bindingContext)
{
string key = bindingContext.ModelName;
ValueProviderResult value = bindingContext.ValueProvider.GetValue (key);
if (value == null || string.IsNullOrEmpty (value.AttemptedValue))
return null;
return RecordIdentity.Parse (value.AttemptedValue);
}
}
public class MyController : Controller
{
// ?identity=Foo_42 will become RecordIdentity { Kind = RecordKind.Foo, RecordId = 42 }
public ActionResult DoSomethingWith (RecordIdentity selectedIdentity)
{
// ...
}
}
In the view template code, you'll need to call Html.DropDownListFor()
with your composite property:
@Html.DropDownListFor(m => m.SelectedIdentity, Model.RowList)
For each value in RowList
or whatever you call your data source, make sure SelectListItem
's Value
property is set to you class' ToString()
output.
The name of object's RecordIdentity
-typed property specified in Html.DropDownListFor
should match action parameter name. Otherwise you'll need to specify dropdown name in Bind
attribute's Prefix
property as seen here:
public ActionResult DoSomethingWith ([Bind(Prefix="SelectedIdentity")] RecordIdentity identity)
{
// do something you need
}
Also note that it will also work if you want RecordIdentity
to be a property of form model or whatever class that you'd like to accept as a parameter for your action method. The binder will get called either way.
By following this approach, you make single class responsible for formatting and parsing which is largely invisible and provides an abstraction over actual fields. However I need to note that such problem may as well be caused by poor database or business entities organization. Maybe a better solution would be to provide each of the items you listed with its own unique native key.
I think it best to define a new class with 2 properties. Something similar to:
public class KindRecordItem {
public int Id {get; set;}
public string Kind {get; set;}
public int RecordId {get; set;}
public string Text {get; set;}
}
You generate all valid / legal combinations of kind and recordId into a collection, pass that collection to the view, and display the display text in the drop down.
<%= Html.DropDownListFor(m => m.SelectedKindRecordItem,
new SelectList(Model.KindRecordItems, "Id", "Text"),
new SelectListItem { Value = "0", Text = "Please select an option" }) %>
In your controller, SelectedKindRecordItem will be a unique ID that you can look up to find the Kind and RecordId for processing.
Id can be generated as a hash of Kind and RecordId or any other method to make it unique.
精彩评论