开发者

What is a good way to edit two values with a single ASP .NET MVC dropdown?

开发者 https://www.devze.com 2023-03-10 10:02 出处:网络
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

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消