简体   繁体   中英

Combine multiple properties in @html.Editorfor Razor

We have to combine multiple properties into one EditorFor field in a Razor view.

We have the properties Quantity, UnitOfMeasure and Ingredient. These need to be combined so the user can just type what he or she needs ie 10 kg potatoes, instead of entering the information into multiple fields.

Once this is done, we also need autocomplete on the UOM and ingredient properties.

I have created a partial view for this code.

@model IEnumerable<RecipeApplication.Models.RecipeLine>
<div class="form-group">
    @Html.Label("Ingrediënten", htmlAttributes: new { @class = "control-label col-md-2" })
    <div>
        @foreach (var item in Model)
        {

            <p>
                @Html.EditorFor(modelItem => item.Quantity, new { htmlAttributes = new { @class = "form-control-inline" } })
                @Html.EditorFor(modelItem => item.UnitOfMeasure.Abbreviation, new { htmlAttributes = new { @class = "form-control-inline" } })
                @Html.EditorFor(modelItem => item.Ingredient.Name, new { htmlAttributes = new { @class = "form-control-inline" } })
            </p>

        }
    </div>

</div>

Obviously this is not the intention.

And this is the code for the Edit functions:

    public ActionResult Edit(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        RecipeModel recipeModel = db.Recipes.Find(id);
        if (recipeModel == null)
        {
            return HttpNotFound();
        }

        GetRecipeLines(id);

        return View(recipeModel);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit([Bind(Include = "Name,Description,ImageUrl")] RecipeModel recipeModel, int?id)
    {
        if (ModelState.IsValid)
        {
            db.Entry(recipeModel).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        GetRecipeLines(id);

        return View(recipeModel);
    }

I have looked on Google and StackOverflow but I can't find a proper answer to get this done.

Personally I wouldn't even know where to start at this moment.

I hope someone can help figuring this out.

Thanks.

Add a new getter property on ReceipLine

C# 6.0 Syntax:

public string QuantityUomIngredient =>
$"{Quantity} {UnitOfMeasure?.Abbreviation ?? ""} {Ingredient?.Name ?? ""}";

Then your view should look like this

@Html.EditorFor(modelItem => item.QuantityUomIngredient ...

And then build a custom model binder to parse QuantityUomIngredient into its corresponding properties (this part should be fun to implement). But be sure to do a good validation on the input so you have good data to parse.

Thanks for the anwer Leo Nix, it surely put me into the right direction.

Here is the code I wrote so far and it seems to work like a charm. (I did not include error handling yet.)

public class RecipeLine
{
    [Key]
    public int RecipeLineId { get; set; }
    public int RecipeId { get; set; }
    public double Quantity { get; set; }
    public virtual UnitOfMeasureModel UnitOfMeasure { get; set; }
    public virtual IngredientModel Ingredient { get; set; }
    public string QuantityUomIngredient => $"{Quantity} {UnitOfMeasure?.Abbreviation ?? ""} {Ingredient?.Name ?? ""}";
}

And the custom Binder I wrote. This one took quite some extra research.

 class RecipeLineCustomBinder : DefaultModelBinder
    {
        private RecipeApplicationDb db = new RecipeApplicationDb();

        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            HttpRequestBase request = controllerContext.HttpContext.Request;

            // Get the QuantityCustomIngredient from the webform. 
            string quantityUomIngredient = request.Form.Get("QuantityUomIngredient");
            // Get the IngredientID from the webform.
            int recipeID = int.Parse(request.Form.Get("RecipeId"));
            // Split the QuantityCustomIngredient into seperate strings. 
            string[] quantityUomIngredientArray = quantityUomIngredient.Split();
            //string[] quantityUomIngredientArray = quantityUomIngredient.Split(new string[] { " " }, 2, StringSplitOptions.RemoveEmptyEntries);

            if (quantityUomIngredientArray.Length >= 3)
            {
                // Get the quantity value
                double quantityValue;
                bool quantity = double.TryParse(quantityUomIngredientArray[0], out quantityValue);

                // Get the UOM value. 
                string uom = quantityUomIngredientArray[1];
                UnitOfMeasureModel unitOfMeasure = null;
                bool checkUOM = (from x in db.UnitOfMeasures
                                 where x.Abbreviation == uom
                                 select x).Count() > 0;
                if (checkUOM)
                {
                    unitOfMeasure = (from x in db.UnitOfMeasures
                                     where x.Abbreviation == uom
                                     select x).FirstOrDefault();
                }

                // Get the ingredient out of the array.
                string ingredient = "";
                for (int i = 2; i < quantityUomIngredientArray.Length; i++)
                {
                    ingredient += quantityUomIngredientArray[i];
                    if (i != quantityUomIngredientArray.Length - 1)
                    {
                        ingredient += " ";
                    }
                }

                bool checkIngredient = (from x in db.Ingredients where x.Name == ingredient select x).Count() > 0;
                IngredientModel Ingredient = null;
                if (checkIngredient)
                {
                    Ingredient = (from x in db.Ingredients
                                  where x.Name == ingredient
                                  select x).FirstOrDefault();
                }

                // Return the values. 
                return new RecipeLine
                {
                    Quantity = quantityValue,
                    UnitOfMeasure = unitOfMeasure,
                    Ingredient = Ingredient,
                    RecipeId = recipeID
            };
            }
            else
            {
                return null;
            }

        }
    }

In the Razor view this is the code that I used:

    <div class="form-group">
        @Html.LabelFor(model => model.QuantityUomIngredient, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.QuantityUomIngredient, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.QuantityUomIngredient, "", new { @class = "text-danger" })
        </div>
    </div>

I added the custom binder in Global.asax.cs

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        ModelBinders.Binders.Add(typeof(RecipeLine), new RecipeLineCustomBinder());
    }
}

And finally added the custom binder to the controller

    [HttpPost]
    public ActionResult Create([ModelBinder(typeof(RecipeLineCustomBinder))] RecipeLine recipeLine)
    {
        if (ModelState.IsValid)
        {
            db.RecipeLines.Add(recipeLine);
            db.SaveChanges();
            return RedirectToAction("Index", new { id = recipeLine.RecipeId });
        }
        return View(recipeLine);
    }

I hope this will help other developers as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM