Here is my scenario: I have a model called MaterialPaymentRequest. It consists of several MaterialPaymentRequestSubItem so PaymentRequest is parent and MaterialPaymentRequestSubItem are its children.
Consider when I have a MaterialPaymentRequest and I want to add a child to it.
Currently its method inside MaterialPaymentRequestSbuItemController looks like this:
public ActionResult CreateChild(int parentId)
{
if (parentId==null)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
var parenetRequest = (from request in db.MaterialPaymentRequests
where request.Id==parentId
select request);
ViewBag.MaterialPaymentRequestId = new SelectList(parenetRequest, "Id", "Description", parentId);
ViewBag.ParentID = parentId;
return View();
}
My problem is inside view, user can change its parent since I have a dropdown which I cannot freeze it or make it readonly:
#Html.DropDownList("MaterialPaymentRequestId", String.Empty)
I've tried to use a ViewModel and after post I set parentID of my chil however this way I don't know how to pass ParentId to http-post controller method.
My postback method before using ViewMode is like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateChild([Bind(Include = "Id,Name,Unit,UnitPrice,MaterialPaymentRequestId,Quantity")] MaterialPaymentRequestSubItem materialpaymentrequestsubitem)
{
if (ModelState.IsValid)
{
...
}
....
}
I've seen methods like this which use Html.Hidden but i think it is not secure enough since user can manipulate information at user-side.
Are there any better method to do this?
Can I pass information to a controller which accept parentID as parameter with declarion like this?
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateChild(int parentID, [Bind(Include = "Id,Name,Unit,UnitPrice,MaterialPaymentRequestId,Quantity")] MaterialPaymentRequestSubItem materialpaymentrequestsubitem)
{
if (ModelState.IsValid)
{
...
}
....
}
To make a HTML helper input control read only on client side, use the following:
#Html.DropDownList("MaterialPaymentRequestId", String.Empty, new { #readonly = "readonly" })
I suggest you to maintain parentId content state in server side with Session variable if you don't want to use client-side hidden fields.
Session["parentId"] = parentId;
// just an example to extract Session variable
int parentId = Session["parentId"] != null ? Convert.ToInt32(Session["ParentId"]) : return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
However if you need the hidden field inside your view, use encoded text in hidden field with validation schema against its value (e.g. Base64 or better encodings), thus any user visiting your site can't easily alter the value in client-side.
View:
#Html.Hidden("ParentID", #ViewBag.ParentID);
// or if you have a viewmodel, pass viewmodel's value here
#Html.HiddenFor(model => model.ParentID);
Controller method:
public ActionResult CreateChild(int parentId)
{
...
// convert parentId into Base64
ViewBag.ParentID = Convert.ToBase64String(parentId);
return View(ViewBag); // model binding
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateChild([Bind(Include = "Id,Name,Unit,UnitPrice,MaterialPaymentRequestId,Quantity,ParentID")] MaterialPaymentRequestSubItem materialpaymentrequestsubitem)
{
...
// get Parent ID
int parentId = (int)Convert.FromBase64String(ParentID);
// write your own algorithm to validate hidden field's value
...
if (ModelState.IsValid)
{
// redirect to create child elements
return RedirectToAction("CreateChild", "Controller", new { #id = parentId });
}
}
Hope this explanation useful for you, CMIIW.
Related
I've got a DropDownListFor which displays a list of Events for me. I'm trying to POST back the ID of the selected event. The EventId is always coming back as 0. (Please ignore the mess of no service layer or repositories, it's just a sample project.)
My Model:
public List<SelectListItem> Events { get; set; }
public int EventId { get; set; }
My View:
#Html.DropDownListFor(m=> m.EventId, Model.Events, new { #class = "form-control" })
Here is how I am populating Events in the controller:
[HttpGet]
public ActionResult Create()
{
//Setting up Events for the DropDownListFor
var events = db.Events.ToList().Select(e => new SelectListItem
{
Text = e.EventName,
Value = e.EventId.ToString()
}).ToList();
return View(new User {Events = events });
}
And in my POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "UserId,FirstName,LastName,Age,VehicleMake,VehicleModel,VehicleYear,AreGuestsAttending,TotalGuests")] User user)
{
if (ModelState.IsValid)
{
db.Users.Add(user);
db.SaveChanges();
var enrollUserForEvent = new Enrollment
{
UserId = user.UserId,
EventId = user.EventId
};
_enrollmentController.Create(enrollUserForEvent);
return RedirectToAction("Index", "Event");
}
return View(user);
}
I've tried playing around with several different things like:
var eventIdString = user.Events.First(e => e.Selected == true).Value;
int eventId = Int32.Parse(eventIdString);
But in the above scenario user.Events is null.
After playing around and searching around I cannot find anything on what I may be doing wrong here. Everything else gets posted back just fine so I'm not sure why I'm not getting the EventId to come back with everything else. Any help would be appreciated!
The problem is here
Bind(Include ="...")
You should also include there the EventId. If EventId misses from this, the EventId property of user get's it's default value, which is 0. Including also this to your Bind statement, then when the user model would be created would take the value of EventId from the data you POST and the value you post would be set properly.
I'm new in C#, specifically in MVC.
I want to return a view with a model like that :
public ActionResult Index()
{
return View(db.MyModel.Where(a => a.EmployeID == m_idEmployee));
}
for a specific employee (where m_idEmployee is the id of employee/user, but for the moment, it's a value that I set myself)
I don't use the loggingaccount method from visual studio (not very clear for me)
So I want to know if I can "pass" value in Index to set m_idEmployee (from my View for example).
You can pass the employee id to your Index method like:
public ActionResult Index(int empId)
{
return View(db.MyModel.Where(a => a.EmployeID == empId));
}
you have to change the way you call the Index method accordingly by passing it as query string or alternatively change the verb to post; the model binder will take care of it.
Modelbinder quick ref:
Maps a browser request to a data object. This class provides a
concrete implementation of a model binder.
Change your controller method to:
public ActionResult Index(int employeeId)
Then create an action in your view:
#Html.ActionLink("Text of the link", "Index", "NameOfYourController", new { employeeId = 123 })
you can create a Class return value in a class independente
and call the function in your controller class like this
EmployerModel.class
public findEmployerById(int id){
return db.MyModel.Where(a => a.EmployeID == id);
}
public ActionResult Index(int id)
{
return View(new EmployerModel().findEmployerById(id));
}
and in your view you need to add
#model package.MyModel
I have a Model that I send to the view in a GET method and is bounded successfully to TextFor and ListFor's
e.g.
#Html.ListBoxFor(x => x.MultiChoice, new MultiSelectList(Model.property, "Id", "Name"), new { multiple = "multiple", #class = "multiselect" })
When the user submits the form, the Model is successfully passed back to the POST action method with its properties.
[HttpPost]
public ActionResult POST(Model quiz)
{
string Q1 = quiz.Q1 // = will equal what the user has put in. good
return View("Quiz", quiz);
}
However, when the Model (quiz) is returned to the view, the properties inside the quiz model are NULL, how do I retain the properties that come through to the POST method?
** Edit **
The GET Method
[HttpGet]
public ActionResult Quiz()
{
try
{
Quiz quiz = new Quiz();
// Of course, I could do this in the constructor of the model..
InitialiseQuiz(Quiz);
return View("Quiz", quiz");
}
catch (Exception ex)
{
}
}
Thanks
If I understood well, when you do this:
#Html.ListBoxFor(x => x.MultiChoice, new MultiSelectList(Model.property, "Id", "Name"))
the Razor will create a <select> tag with x.MultiChoice values as selected options. BUT, nowhere will be persisted the Model.property values (as it may be a collection, right?).
So, when you do the POST, you will only send the x.MultiChoice value back to the server, and the Model.property collection will be missed.
Knowing that, you just need to fill this property with the collection again during the POST action, like:
[HttpPost]
public ActionResult POST(Model quiz)
{
// some code here
quiz.property = new CollectionOfSomething();
return View("Quiz", quiz);
}
Is that what you are looking for?
The modelbinder news up an instance of that class with whatever POST data it has. Anything that's null has no posted data. The easiest way to make it not be null, then, is to create an HTML input for that property so that something is posted for it.
However, in situations where you're dealing with existing data, it's preferable to only post what you need to post, and then lookup the original object again in order to map the original property values back onto the version that was passed into your action:
[HttpPost]
public ActionResult Quiz(int id, Quiz model)
{
var quiz = db.Quizzes.Find(id);
// assuming `Foo` was a property that was not posted
model.Foo = quiz.Foo
...
}
I have a view that loads a record with a certain record number. Once the page is loaded, it gives the user an opportunity to login for additional information. Once the login logic is performed, I need to return to that same view with the same record number intact. I am passing the record number to the action using a hidden input in the form. What I can't seem to figure out is how to return to that same view and provide it with that record #. The code I am trying below is not working. I know this is MVC 101 stuff but a hint in the right direction would be appreciated, or feel free to scrap my method and suggest something better!
Form in view:
<form action="/MyView/Authenticate/#item.ID" method="post" enctype="multipart/form-data">
<input name="form_id" type="hidden" value="#item.ID">
.....
Form action:
[HttpPost]
public ActionResult Authenticate()
{
int myid = Convert.ToInt16(Request["form_id"]);
.....
return View("Index", new { id = myid } );
}
EDIT:
It turns out that the correct view is being returned, but it is expecting a model item type of "JobSummaryModel" per the Index action result below. So the question I actually need answered is, how do I pass both the record id and this view model to it?
public ActionResult Index(int id = 0)
{
List<JobSummaryModel> jdata;
ViewBag.IsResults = false;
if (id != 0)
{
ViewBag.IsResults = true;
}
jdata = db.Jobs.Where(c => c.ID == id).Select(c => new JobSummaryModel() { ID = c.ID, Name = c.Name, City = c.City, PostalCode = c.PostalCode, JobDescription = c.PositionDescription }).ToList();
return View(jdata);
}
EDIT:
Thanks Reddy, your suggestions worked! My only remaining issue is that when I return to my Index view from the Authenticate action, I do not seem to have my "jdata". Is my Index action result not being rerun when I return the Index view via my Authenticate action? I am coming from a web forms background where, in an instance like this, the Load/Init events would automatically run when a form is loaded. Do I need to bind my "jdata" in the Authenticate action and include it in the viewmodel?
EDIT: Resolved. Changed my "return View" to a "return RedirectToAction" to resolve my final issue. Thanks everyone!
Answer For your after Edit:
All you want to pass to view is a int Id and your List<JobSummaryModel> jdata right?
So create a ViewModel JObSummaryModelHelper
Public class JObSummaryModelHelper
{
public int Id {get;set;}
public List<JobSummaryModel> jdata {get;set;}
}
Now in your controller
public ActionResult Index(int id = 0)
{
JObSummaryModelHelper jobDetails = new JObSummaryModelHelper();
jobDetails.Id = id;
ViewBag.IsResults = false;
if (id != 0)
{
ViewBag.IsResults = true;
}
jobDetails .jdata = db.Jobs.Where(c => c.ID == id).Select(c => new JobSummaryModel() { ID = c.ID, Name = c.Name, City = c.City, PostalCode = c.PostalCode, JobDescription = c.PositionDescription }).ToList();
return View(jobDetails );
}
Now make sure your view is set to expect this new viewmodel
#model JObSummaryModelHelper
carry on with your manipulation......
You are better off creating a ViewModel for this like so:
Create a View Model class i.e.
public class AuthViewModel
{
public int MyId { get; set; }
}
In your View put the following directive at the top:
#model AuthViewModel
In your initial [HttpGet] method return the view model:
[HttpGet]
public ActionResult Authenticate()
{
var model = new AuthViewModel { MyId = 123 };
return View("Index", model );
}
It's best to use Html helpers in your view, so you can change it to this:
#using(Html.BeginForm()
{
#Html.HiddenFor(m => m.MyId)
...
}
The above uses naming conventions to post back to the action that you are on.
Then return it to your view like this:
[HttpPost]
public ActionResult Authenticate(AuthViewModel model)
{
int myid = model.MyId;
return View("Index", model );
}
Then you can output using this razor syntax #Model.MyId
It's really worth doing some tutorials to learn the conventions, a small amount of time invested in this will save you a lot of time in the future.
Instead of
return View("Index", new { id = myid } );
could you do
return Index(myid);
I have a simple Action in my Controller:
public ActionResult Edit(int itemId)
{
return View(new EditModel() { ItemId = itemId + 1 });
}
public class EditModel
{
public int ItemId { get; set; }
public string Title { get; set; }
}
The problem comes in the View, when I try to display everything.
Model.ItemId: #Model.ItemId
#Html.EditorForModel()
Since action parameter and property on EditModel have the same name (itemId) I get the following:
Is this a correct behaviour? How can I change default value displayed inside a form in that case?
This may be somehow confusing for the first look, but yes, this is default(correct) behavior. Controller.ModelState is the privileged supplier for the values when you use EditorFor or similar editor helpers, over the model itself. But there's trickier point in your situation.
ModelState is populated with action parameters and values that take part in model binding. When you call that action, ModelState is populated with "ItemId" = action parameter(itemId) value. Later, EditorFor sees that is should draw editor for ItemId. As ModelState has already got ItemId, it does not look at model value, but extracts it from ModelState["ItemId"]. Tricky source of your error is that action parameter name matches the model property name and modelState prefers that over model value. The best solution (clean one) would be to just rename action parameter, so that it does not match the model property name.
public ActionResult Edit(int initialItemId)
{
return View(new EditModel() { itemId = initialItemId + 1 });
}
This will do the trick.
You can write
Model.ItemId: #Model.ItemId
#Html.EditorFor(x => x.Title)
Or hide ItemId for edit with metadata