2

I am trying to figure out how to shorten my code but I'm stuck on this issue, please take a look at this.

I have a form like

@using (Html.BeginUmbracoForm("Save", "Student", FormMethod.Post))
{
  <input type="hidden" name="Id" />
  <input type="text" name="Name" />
  <input type="text" name="NickName" />
  <input type="text" name="Age" />
  <input type="text" name="Address" />
  <input type="submit" />
}

while here is my controller

public ActionResult Save(Student s) {
   var record = _db.Students.Find(s.Id);
   if(record != null) {
      record.Id = s.Id;
      record.Name = s.Name;
      record.NickName = s.NickName;
      record.Age = s.Age;
      record.Address = s.Address;
      _db.SaveChanges();
   }
   return RedirectToCurrentUmbracoUrl():
 }

and finally my Model

public class Student() {
  public int Id { get; set; }
  public string Name { get; set; }
  public string NickName { get; set; }
  public int Age { get; set; }
  public int Address { get; set }
}

Alright, above code works fine, no problem on it.

My Problem

I have different views, where I only update some of the Student's class properties, and I want to use the same Controller. See my 3 forms below

@using (Html.BeginUmbracoForm("Save", "Student", FormMethod.Post))
{
    <input type="hidden" name="Id" />
    <input type="text" name="Name" />
    <input type="submit" />
}

@using (Html.BeginUmbracoForm("Save", "Student", FormMethod.Post))
{
    <input type="hidden" name="Id" />
    <input type="text" name="Age" />
    <input type="submit" />
}

@using (Html.BeginUmbracoForm("Save", "Student", FormMethod.Post))
{
    <input type="hidden" name="Id" />
    <input type="text" name="Address" />
    <input type="submit" />
}

Now the problem is that if I submit the first form, then the Student Age and Student Address will contain null or empty because they have no values.

So how can I do it without creating three Controllers like:

  • public ActionResult SaveStudentName(),
  • public ActionResult SaveStudentAge() and
  • public ActionResult SaveStudentAddress()

that save specific Student detail ?

I only want to use the controller public ActionResult Save() where all actions related to saving Student detail must forward here.

Sorry for my bad english, I hope somebody can give me a tip.

2 Answers 2

1

Unfortunately there's no way to achieve what you're looking for here. @apomene's answer would technically work, as long as you could make the properties nullable or could rely on the default value of something like an int (0) not actually being a valid value. For example, with age you could do:

if (s.Age != default(int))

But that assumes that 0 is never a valid value. In this case, it's probably okay, but it might not always. The big problem with this approach, though, is that it allows tampering. For example, if all you're allowing to be edited is Age, that doesn't stop a malicious user from editing the page to add a field for say Name. On post this maliciously added value would be saved just as well.

So, what you need, really is a specific view model for each variation of the form, which then means a specific action that accepts that particular view model. In a typical object-oriented scenario, you may at this point be tempted to say, OK, I'll just use a base class or interface that all of them can implement. Unfortunately, based on the way the routing framework works and the fact that the action has to be invoked by passing a specific instance of a specific class composed from data that was posted, there's simply no reliable way to use a base class or an interface.

Long and short, you either need separate actions for each variation or you must allow any and all data on the model to be potentially edited, regardless of what specific fields you choose to include on the page. There is no other option.

EDIT

It's worth noting that just because you need separate actions doesn't mean you have to necessarily duplicate code. You could always do something like:

public ActionResult EditAge(int id, AgeViewModel model)
{
    UpdateStudent(id, age: model.Age);
    return View();
}

public ActionResult EditName(int id, NameViewModel model)
{
    UpdateStudent(id, name: model.Name);
    return View();
}

...

protected void UpdateStudent(int id, string name = null, int? age = null)
{
    var student = db.Students.Find(id);
    if (name != null)
    {
        student.Name = name;
    }
    if (age.HasValue)
    {
        student.Age = age.Value;
    }

    db.Entry(student).State = EntityState.Modified;
    db.SaveChanges();
}

That's very simplified form handling of course, but the primary idea here is that your student saving code can be factored out and each action can then just call this common method, while still accepting unique view models.

Sign up to request clarification or add additional context in comments.

1 Comment

I think I appreciate more the answer of "there's no way to achieve that" instead of a "hack" solution.
0

I would add enum:

public enum StudentEditMode { Full, Age, Address, Name, NickName }

and add it to your ViewModel:

public class Student() {
    //...
    public StudentEditMode EditMode {get; set;}
}

Then your controller method will look like:

public ActionResult Save(Student s) {
   var record = _db.Students.Find(s.Id);
   if(record != null) {
      bool isFullEdit = record.EditMode == StudentEditMode.Full;
      if (isFullEdit || record.EditMode == StudentEditMode.Name)
         record.Name = s.Name;
      if (isFullEdit || record.EditMode == StudentEditMode.NickName)
         record.NickName = s.NickName;
      if (isFullEdit || record.EditMode == StudentEditMode.Age)
          record.Age = s.Age;
      if (isFullEdit || record.EditMode == StudentEditMode.Address)
          record.Address = s.Address;
      _db.SaveChanges();
   }
   return RedirectToCurrentUmbracoUrl():
 }

And view:

@using (Html.BeginUmbracoForm("Save", "Student", FormMethod.Post))
{
    <input type="hidden" name="Id" />
    <input type="hidden" name="Mode" value="@StudentEditMode.Name" />
    <input type="text" name="Name" />
    <input type="submit" />
}

@using (Html.BeginUmbracoForm("Save", "Student", FormMethod.Post))
{
    <input type="hidden" name="Id" />
    <input type="hidden" name="Mode" value="@StudentEditMode.Age" />
    <input type="text" name="Age" />
    <input type="submit" />
}

@using (Html.BeginUmbracoForm("Save", "Student", FormMethod.Post))
{
    <input type="hidden" name="Id" />
    <input type="hidden" name="Mode" value="@StudentEditMode.Address" />
    <input type="text" name="Address" />
    <input type="submit" />
}

3 Comments

hi thank you Serhiyb, but it seems like a hack. Do we have another cleaner solution, wherein we don't need to touch the View part.
The other option is to check Request.Form.AllKeys.Contains("Name" /*etc*/) before updating/setting Name field and same for others. It will contain less code and no need to change views but this approach is less transparent IMHO.
Also doesn't prevent form tampering.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.