Update single Property on EF4 Entity without having have hidden fields - asp.net-mvc-3

I am using EF4 (Db First) and I have an entity with a number of non-nullable properties.
In an Edit form (Razor/MVC3), I want to allow the editing of only one of the properties, but not the others.
To get this to work, I am having to put #Html.HiddenFor(...) statements for each of my other properties that can't be nullable, otherwise I get an error on SaveChanges().
Is there a simple way to just have the ID hidden on the view, the property that can be edited, and then update ONLY that property?

All you need to do in this case is to include the ID of the entity you are editing as a hidden field as well as a text field for the property that you actually wanna edit:
#using (Html.BeginForm())
{
#Html.HiddenFor(x => x.ID)
#Html.EditorFor(x => x.PropertyYouWannaEdit)
<button type="submit">Update</button>
}
and then in the corresponding controller action you could retrieve the entity that needs to be edited from your database, update the value of the property that needs editing and save back the changes.
[HttpPost]
public ActionResult Update(SomeEntity model)
{
SomeEntity entityToEdit = db.GetEntity(model.ID);
entityToEdit.PropertyYouWannaEdit = model.PropertyYouWannaEdit;
db.Update(entityToEdit);
return RedirectToAction("Success");
}
But personally I would use a view model and AutoMapper to handle this situation. So I would start by designing a view model representing the requirements of my view and including the properties that needs to be edited only:
public class MyEntityViewModel
{
public int ID { get; set; }
public string Property1ToBeEdited { get; set; }
public string Property2ToBeEdited { get; set; }
...
}
and then have the corresponding view:
#model MyEntityViewModel
#using (Html.BeginForm())
{
#Html.HiddenFor(x => x.ID)
#Html.EditorFor(x => x.Property1ToBeEdited)
#Html.EditorFor(x => x.Property2ToBeEdited)
...
<button type="submit">Update</button>
}
and finally the 2 controller actions (GET and POST):
public ActionResult Update(int id)
{
// Fetch the domain model that we want to be edited from db
SomeEntity domainModel = db.GetEntity(id);
// map the domain model to a view model
MyEntityViewModel viewModel = Mapper.Map<SomeEntity, MyEntityViewModel>(domainModel);
// pass the view model to the view
return View(viewModel);
}
[HttpPost]
public ActionResult Update(MyEntityViewModel model)
{
if (!ModelState.IsValid)
{
// validation failed => redisplay view so that the user can fix the errors
return View(model);
}
// fetch the domain entity that needs to be edited:
SomeEntity entityToEdit = db.GetEntity(model.ID);
// update only the properties that were part of the view model,
// leaving the others intact
Mapper.Map<MyEntityViewModel, SomeEntity>(model, entityToEdit);
// persist the domain model
db.Update(entityToEdit);
// we are done
return RedirectToAction("Success");
}

Related

How to Retrieve Page Hyperlinks after Requesting a URL using MVC3?

I have created a very simple view in my MVC3 project that contains a textbox that receives and validates a URL. The controller class is rather simple:
[HttpPost]
public ActionResult Save(ValidationModel model)
{
if (ModelState.IsValid)
{
//Save or whatever
}
return View(model);
}
I'm needing some guidance on how to retrieve the URL entered into the textbox, and subseuquently scan the resulting page for hyperlinks or tags. Once those tags are scanned, I need to return a new view to my user with a list or grid of the tags in alpha order.
Can anyone point me in the correct direction on above steps?
Thanks:)
In your view model you will have a property:
public class ValidationModel
{
[Required]
public string Url { get; set; }
}
and then you will have a corresponding textbox in the view:
#model ValidationModel
#using (Html.BeginForm)
{
#Html.EditorFor(x => x.Url)
<button type="submit">OK</submit>
}
and finally in your POST controller action:
[HttpPost]
public ActionResult Save(ValidationModel model)
{
if (ModelState.IsValid)
{
//Save or whatever
// use model.Url here => it will contain the user input
}
return View(model);
}
Try this:
in your view where your using your model inside your FORM:
#Html.TextBoxFor(m => m.MyHyperLink)
and in your controller you do this:
model.MyHyperLink you can manipulate the string or do what ever you want
easy as that..
hope i helped.

Read The Data From DropDown in ASP.Net MVC3

I am doing the ASP.net MVC 3 (Empty type and not the internet type) with the Database First approach...
What i need is
Step 1:
I just used the dropdown to display the various locations where the company is located. The list comes from the Organization table and Location is only one string field in this Oranization Table,
Step 2:
While the user is doing registration, the dropdown list will show the locations.. Now, user selects India, then this value (Location Name) should store in the UserLogin Table...
Now how to read the value from the dropdown and i hope you understand my question and thanks in advance
I would use view models:
public class RegisterViewModel
{
public string LocationName { get; set; }
public IEnumerable<SelectListItem> Locations { get; set; }
}
then a controller action that will serve the view:
public ActionResult Index()
{
var model = new RegisterViewModel();
model.Locations = new SelectList(dbcontext.Organization_Details, "OName", "OLocation");
return View(model);
}
then the corresponding strongly typed view:
#model RegisterViewModel
#using (Html.BeginForm())
{
#Html.LabelFor(x => x.LocationName)
#Html.DropDownListFor(x => x.LocationName, Model.Locations)
<button type="submit">OK</button>
}
and finally the controller action that will be invoked when the form is submitted:
[HttpPost]
public ActionResult Index(RegisterViewModel model)
{
// model.LocationName will contain the selected location here
...
}

Need help to explain Readonly\ScaffoldColumn(false)

Please help with such a question and do not judge strictly because I'm a newbie in MVC:
I've got a model for storing names of users by ID in my DB
public class Names
{
public int NameId { get; set; }
public string Username { get; set; }
}
,
a conrtoller
[HttpPost]
public ActionResult EditforModel(Names Name)
{
if (ModelState.IsValid)
{
db.Entry(Name).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(Name);
}
adding and editing view
adding is working well, the question is about editing
I use
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend> legend </legend>
#Html.EditorForModel()
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
to edit my model.
when trying to go to this view I see an editor for both Id and Username, but if i fill Id - I've got error, because there is no Entry in DB with such Id.
Ok.Let's look for attributes to hide an editor.
[ScaffoldColumn(false)] is something like a marker whether to render an editor for Id or not.
applaying it to my model I've got "0" id posting from my View.Try another attr.
[ReadOnly(true)] makes a field a readonly-field. But at the same time I've got "0" in posting Id.
Modifying a view I placed an editors for each field in model
#Html.HiddenFor(model => model.NameId)
#Html.EditorFor(model => model.Username)
but using it is dangerous because some user can post wrong Id throgh post-request.
I can't use [ScaffoldColumn(false)] with applying Id at [Httppost] action of the controller,by searching appropriate user-entry in DB, because the name was changed..
I can't believe #Html.HiddenFor is the only way out.But can't find one :(
As you mentioned "[ScaffoldColumn(false)] is something like a marker whether to render an editor for Id or not", and [ReadOnly(true)] means that this property will be excluded by the default model binder when binding your model.
The problem is that the HTTP protocol is a stateless protocol, which means that when the user posts the edit form to the MVC Controller, this controller has no clue which object he was editing, unless you include some identifier to your object in the request received from the user, though including the real object Id isn't a good idea for the reason you mentioned (that someone could post another Id).
A possible solution might be sending a View Model with an encrypted Id to the View, and decrypting this Id in the controller.
A View Model for your object might look like this :
public class UserViewModel
{
[HiddenInput(DisplayValue = false)]
public string EncryptedId { get; set; }
public string Username { get; set; }
}
So your HttpGet action method will be
[HttpGet]
public ActionResult EditforModel()
{
// fetching the real object "user"
...
var userView = new UserViewModel
{
// passing the encrypted Id to the ViewModel object
EncryptedId = new SimpleAES().EncryptToString(user.NameId.ToString()),
Username = user.Username
};
// passing the ViewModel object to the View
return View(userView);
}
Don't forget to change the model for your View to be the ViewModel
#model UserViewModel
Now the HttpPost action method will be receiving a UserViewModel
[HttpPost]
public ActionResult EditforModel(UserViewModel Name)
{
if (ModelState.IsValid)
{
try
{
var strId = new SimpleAES().DecryptString(Name.EncryptedId);
var id = int.Parse(strId);
// select the real object using the decrypted Id
var user = ...Single(p => p.NameId == id);
// update the value from the ViewModel
user.Username = Name.Username;
db.Entry(user).State = EntityState.Modified;
}
catch (CryptographicException)
{
// handle the case where the encrypted key has been changed
return View("Error");
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(Name);
}
When the user tries to change the encrypted key, the decryption will fail throwing a CryptographicException where you can handle it in the catch block.
You can find the SimpleAES encryption class here (don't forget to fix the values of Key and Vector arrays):
Simple insecure two-way "obfuscation" for C#
PS:
This answer is based on the following answer by Henry Mori:
Asp.net MVC 3 Encrypt Hidden Values

IEnumerable property with MVC3 EditorTemplate

Similar to this post IEnumerable model property in an ASP.NET MVC 3 Editor Template, I have
Model
public class Student
{
public int StudentId { get; set; }
public string StudentName{ get; set; }
//FYI..Its virtual because of EF relationship
public virtual ICollection<Class> Classes{ get; set; }
}
public class Class
{
public int ClassId { get; set; }
public string ClassName{ get; set; }
}
View - EditStudent
#model Student
#Html.TextBoxFor(m => m.StudentName)
//I get the error for following..see below
#Html.EditorFor(m => m.Classes);
Student/EditorTemplates/Class
#model Class
<div>
#*checkbox here*#
#Html.LabelFor(x => x.ClassName)
</div>
Controller
public ActionResult EditStudent(int id)
{
ViewBag.Classes = repository.GetClasses();
Student student = repository.GetStudent(id);
return View("EditStudent", student);
}
Error in View on statement #Html.EditorFor(m => m.Classes); is..
The model item passes into the dictionary is of type
'System.Collections.Generic.HashSet`1[Class]', but this dictionary
required a model item of type 'Class'.
Basically, what I am trying to achieve is to display the list of all classes available with a checkbox next to it ( I have not reached to that part of code yet). Then check all classes to a student is enrolled and allow to change the selections.
How do I display the list of checkboxes with the given Model.
Should I bind my EditorTemplate with ViewBag.Classes (How?) or ?
I need to get selected checkbox values in Post ActionMethod as well.
I read some posts those suggest to create CheckBoxListHelper, but it should be possible to do with EditorTemplate as I need to display a simple list.
Please suggest. Thanks.
Okay, I figured it out. Thanks to very precise post here
How to provide an EditorTemplate for IEnumerable<MyModel>?
First, I renamed the EditorTemplate to StudentClass - not sure if this has anything to do with binding or not, but I did.
Second, modified EditorTemplate to bind with IEnumerable
#model IEnumerable<Class>
var checked = "";
#foreach (Class class in ViewBag.Classes)
{
if (Model != null)
{
Class class = Model.FirstOrDefault(c => c.ClassId.Equals(class.ClassId));
if (class != null)
{
checked = "checked=checked";
}
}
<input type="checkbox" name="Classes" value="#class.ClassId" #checked />
#class.ClassName
}
And I call the template with name
#Html.EditorFor(m => m.Classes, "StudentClass");
Now in controller's Post method I can get the array of Classes (name of checkboxes).

Two models in one view in ASP MVC 3

I have 2 models:
public class Person
{
public int PersonID { get; set; }
public string PersonName { get; set; }
}
public class Order
{
public int OrderID { get; set; }
public int TotalSum { get; set; }
}
I want edit objects of BOTH classes in SINGLE view, so I need something like:
#model _try2models.Models.Person
#model _try2models.Models.Order
#using(Html.BeginForm())
{
#Html.EditorFor(x => x.PersonID)
#Html.EditorFor(x => x.PersonName)
#Html.EditorFor(x=>x.OrderID)
#Html.EditorFor(x => x.TotalSum)
}
This, of course, don't work: Only one 'model' statement is allowed in a .cshtml file. May be there is some workaround?
Create a parent view model that contains both models.
public class MainPageModel{
public Model1 Model1{get; set;}
public Model2 Model2{get; set;}
}
This way you can add additional models at a later date with very minimum effort.
To use the tuple you need to do the following, in the view change the model to:
#model Tuple<Person,Order>
to use #html methods you need to do the following i.e:
#Html.DisplayNameFor(tuple => tuple.Item1.PersonId)
or
#Html.ActionLink("Edit", "Edit", new { id=Model.Item1.Id }) |
Item1 indicates the first parameter passed to the Tuple method and you can use Item2 to access the second model and so on.
in your controller you need to create a variable of type Tuple and then pass it to the view:
public ActionResult Details(int id = 0)
{
Person person = db.Persons.Find(id);
if (person == null)
{
return HttpNotFound();
}
var tuple = new Tuple<Person, Order>(person,new Order());
return View(tuple);
}
Another example : Multiple models in a view
Another option which doesn't have the need to create a custom Model is to use a Tuple<>.
#model Tuple<Person,Order>
It's not as clean as creating a new class which contains both, as per Andi's answer, but it is viable.
If you are a fan of having very flat models, just to support the view, you should create a model specific to this particular view...
public class EditViewModel
public int PersonID { get; set; }
public string PersonName { get; set; }
public int OrderID { get; set; }
public int TotalSum { get; set; }
}
Many people use AutoMapper to map from their domain objects to their flat views.
The idea of the view model is that it just supports the view - nothing else. You have one per view to ensure that it only contains what is required for that view - not loads of properties that you want for other views.
ok, everyone is making sense and I took all the pieces and put them here to help newbies like myself that need beginning to end explanation.
You make your big class that holds 2 classes, as per #Andrew's answer.
public class teamBoards{
public Boards Boards{get; set;}
public Team Team{get; set;}
}
Then in your controller you fill the 2 models. Sometimes you only need to fill one. Then in the return, you reference the big model and it will take the 2 inside with it to the View.
TeamBoards teamBoards = new TeamBoards();
teamBoards.Boards = (from b in db.Boards
where b.TeamId == id
select b).ToList();
teamBoards.Team = (from t in db.Teams
where t.TeamId == id
select t).FirstOrDefault();
return View(teamBoards);
At the top of the View
#model yourNamespace.Models.teamBoards
Then load your inputs or displays referencing the big Models contents:
#Html.EditorFor(m => Model.Board.yourField)
#Html.ValidationMessageFor(m => Model.Board.yourField, "", new { #class = "text-danger-yellow" })
#Html.EditorFor(m => Model.Team.yourField)
#Html.ValidationMessageFor(m => Model.Team.yourField, "", new { #class = "text-danger-yellow" })
And. . . .back at the ranch, when the Post comes in, reference the Big Class:
public ActionResult ContactNewspaper(teamBoards teamboards)
and make use of what the model(s) returned:
string yourVariable = teamboards.Team.yourField;
Probably have some DataAnnotation Validation stuff in the class and probably put if(ModelState.IsValid) at the top of the save/edit block. . .
In fact there is a way to use two or more models on one view without wrapping them in a class that contains both.
Using Employee as an example model:
#model Employee
Is actually treated like.
#{ var Model = ViewBag.model as Employee; }
So the View(employee) method is setting your model to the ViewBag and then the ViewEngine is casting it.
This means that,
ViewBag.departments = GetListOfDepartments();
return View(employee);
Can be used like,
#model Employee
#{
var DepartmentModel = ViewBag.departments as List<Department>;
}
Essentially, you can use whatever is in your ViewBag as a "Model" because that's how it works anyway. I'm not saying that this is architecturally ideal, but it is possible.
Just create a single view Model with all the needed information in it, normaly what I do is create a model for every view so I can be specific on every view, either that or make a parent model and inherit it. OR make a model which includes both the views.
Personally I would just add them into one model but thats the way I do it:
public class xViewModel
{
public int PersonID { get; set; }
public string PersonName { get; set; }
public int OrderID { get; set; }
public int TotalSum { get; set; }
}
#model project.Models.Home.xViewModel
#using(Html.BeginForm())
{
#Html.EditorFor(x => x.PersonID)
#Html.EditorFor(x => x.PersonName)
#Html.EditorFor(x => x.OrderID)
#Html.EditorFor(x => x.TotalSum)
}
You can use the presentation pattern http://martinfowler.com/eaaDev/PresentationModel.html
This presentation "View" model can contain both Person and Order, this new
class can be the model your view references.
Another way that is never talked about is
Create a view in MSSQL with all the data you want to present. Then use LINQ to SQL or whatever to map it. In your controller return it to the view. Done.
you can't declare two model on one view, try to use Html.Action("Person", "[YourController]") & Html.Action("Order", "[YourController]").
Good luck.
Beside of one view model in asp.net you can also make multiple partial views and assign different model view to every view, for example:
#{
Layout = null;
}
#model Person;
<input type="text" asp-for="PersonID" />
<input type="text" asp-for="PersonName" />
then another partial view Model for order model
#{
Layout = null;
}
#model Order;
<input type="text" asp-for="OrderID" />
<input type="text" asp-for="TotalSum" />
then in your main view load both partial view by
<partial name="PersonPartialView" />
<partial name="OrderPartialView" />
I hope you find it helpfull !!
i use ViewBag For Project and Model for task so in this way i am using two model in single view and in controller i defined viewbag's value or data
List<tblproject> Plist = new List<tblproject>();
Plist = ps.getmanagerproject(c, id);
ViewBag.projectList = Plist.Select(x => new SelectListItem
{
Value = x.ProjectId.ToString(),
Text = x.Title
});
and in view tbltask and projectlist are my two diff models
#{
IEnumerable<SelectListItem> plist = ViewBag.projectList;
}
#model List

Resources