asp.net mvc scaffolding and GUID as the primary key - asp.net-mvc

I have a table with the following structure
GUID uniqueidentifier with default value newid(), and set as primary key
ID int
Description varchar(max)
I created an Entity Model using visual studio, and generated the views for editing/deleting etc (mvc scaffolding)
The problem is with "Editing", when I click that link, the appropriate form is showed with the correct data, but the "save" button doesn't work at all. Please note that other links (delete,create,details) work perfectly..
So, when I click the "Edit" link, the url is
http://localhost:10871/admin/Edit/e7d0c5ee-7782-411f-920e-7b0d93c924e1
and the form is displayed correctly, but the save button doesn't work, no network activity happens. Is something particular about using uniqueidentifers as primary key?
Thanks
---Code---
Edit.cshtml
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Domain</legend>
#Html.HiddenFor(model => model.GUID)
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
--AdminController.cs--
// GET: /Admin/Edit/5
public ActionResult Edit(Guid id)
{
Domain domain = db.Domains.Single(d => d.GUID == id);
return View(domain);
}
//
// POST: /Admin/Edit/5
[HttpPost]
public ActionResult Edit(Domain domain)
{
if (ModelState.IsValid)
{
db.Domains.Attach(domain);
db.ObjectStateManager.ChangeObjectState(domain, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(domain);
}
Edit2
In response to a comment by ZippyV, I added the following code in Edit.cshtml
<div class="editor-label">
#Html.LabelFor(model => model.ID)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ID)
#Html.ValidationMessageFor(model => model.ID)
</div>
to my surprise (or ignorance) - the GUID is being shown instead of ID
and apart from that - when I enter a value in that field (1,2 or any integer), I still get the message "The field ID must be a number."

The problem comes from the fact that you have a property called ID on your view model:
public class Domain
{
public int ID { get; set; }
...
}
and at the same time you have a route parameter called id in your Edit controller action:
public ActionResult Edit(Guid id)
{
...
}
Now here's what happens: The Html helpers (such as TextBoxFor, HiddenFor, ...) always first looks in the model state when binding a value (i.e. query string parameters, route values) and only at the end it looks at the value in the model. That's how all Html helpers work and it is by design.
So you have the following:
#Html.HiddenFor(model => model.ID)
The ID is taken NOT from the model but from your Route data (which is a Guid as specified in your action parameter). That's why it is very dangerous to use properties in the view model and action parameters that have the same name.
One possible workaround would be to rename your action parameter to something else:
public ActionResult Edit(Guid domainId)
{
Domain domain = db.Domains.Single(d => d.GUID == domainId);
return View(domain);
}
Of course now your routes might need to be adapted or use query string parameters: controller/edit?domainId=d578b4b7-48ec-4846-8a49-2120c17b6441 to invoke the Edit action.

Related

Passing data from a View to a Controller in .NET MVC - "#model" not highlighting

The following code works as I need it to:
#using (Html.BeginForm("LOLOL", "PATIENT", null))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PATIENT</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</fieldset>
<p>
<input type="submit" value="SUBMIT" />
</p>
}
In LOLOLController:
[HttpPost]
public IActionResult LOLOL(Patient p) {
var client = new MongoClient("mongodb://localhost:27017");
var userId = _userManager.GetUserId(HttpContext.User);
string db_name = "test" + userId;
var database = client.GetDatabase(db_name);
var collection = database.GetCollection<BsonDocument>("patients");
var filter = Builders<BsonDocument>.Filter.Eq("Name", p.Name.ToString());
var document = collection.Find(filter).First();
// I'm cutting short the rest of the code, because when I do something
// similar later, "collection.Find(filter).First()" fires an exception, I'll
// explain..
return View(p);
}
I have something equivalent to taking off the fieldset element in the HTML, leaving basically just a button in the "Html.BeginForm", but then the data is clearly not binding properly, which I know because if I just have a button and no data-entry, I click the button and then I get an error saying the data cannot be found from the database. (EDIT: I now have confirmed that this is indeed because the Patient object is not being passed to the controller quite as I expected it to, seems like a brand new Patient object was created upon calling html.beginform ... I thought that maybe the old Patient object was being passed so I did not have to enter all its data members every time we use Html.BeginForm)
In sum, I want to fill out a text box, click a button to load a new page and display the value of that textbox, but have that value also persisted in essentially a session state, so that if I call another Html.BeginForm function and go into a third view, the text from the first view will be displayed in the third view, even though I did not have to type its value in the second view. Hopefully I can repeat this process, and essentially load up the data members of a class with one view per data member.
Make sure you pass the data from the previous view to the new view from your Controller. When you pass it, include #HiddenFor for those properties from the previous view in your new view. That way the new view will keep and then pass the values to your next POST.
#Html.HiddenFor(model => model.PropertyYouPassedAndWantToKeepAndPassAgain
Edit: Here's the logic for using multiple views for one object... as requested.
Model:
public class Patient
{
string Name { get; set; }
string Address { get; set; }
string City { get; set; }
}
Page1 GET:
[HttpGet]
public ActionResult Page1()
{
Patient patient = new Patient();
return View("~/Views/Page1.cshtml", patient);
}
Page 1 View... only ask for the name.
#model mysite.Models.Patient
#using (Html.BeginForm("LOLOL", "PATIENT", null))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PATIENT</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</fieldset>
<p>
<input type="submit" value="SUBMIT" />
</p>
}
Page1 POST... get the patient and pass it on to the next view...
[HttpPost]
public ActionResult Page1(Patient patient)
{
if (ModelState.IsValid)
{
return View("~/Views/Page2.cshtml", patient); // pass your patient to the second page view with the name
}
else
{
return View("~/Views/Page1.cshtml", patient);
}
}
Page2 GET... get the patient from the prior Page1 POST and send it off to the Page2 View.
[HttpGet]
public ActionResult Page2(Patient patient)
{
// Receive patient from Page1 post and pass it to new view... includes the name
return View("~/Views/Page2.cshtml", patient);
}
Page2 View gets the object... use a HiddenFor to keep the name which you just sent from the GET.
#model mysite.Models.Patient
#using (Html.BeginForm("LOLOL", "PATIENT", null))
{
#Html.HiddenFor(model => model.Name) #* This will keep the name on your next post *#
#Html.ValidationSummary(true)
<fieldset>
<legend>PATIENT</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Address)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Address)
#Html.ValidationMessageFor(model => model.Address)
</div>
</fieldset>
<p>
<input type="submit" value="SUBMIT" />
</p>
}
Since the HiddenFor holds the Name, it will be passed on your next post. It is there but hidden from the form itself.
[HttpPost]
public ActionResult Page2(Patient patient)
{
// Because of the HiddenFor, the Name will be passed because it was kept in the view... but hidden from the form itself.
// It's basically storing it for you to pass again
if (ModelState.IsValid)
{
// Pass object with Name and Address to next controller
return View("~/Views/Page3.cshtml", patient);
}
else
{
return View("~/Views/Page2.cshtml", patient);
}
}
Page2 POST
[HttpPost]
public ActionResult Page2(Patient patient)
{
// Because of the HiddenFor, the Name will be passed because it was kept in the view... but hidden from the form itself.
// It's basically storing it for you to pass again
if (ModelState.IsValid)
{
// Pass object with Name and Address to next controller
return View("~/Views/Page3.cshtml", patient);
}
else
{
return View("~/Views/Page2.cshtml", patient);
}
}
Page3 GET
[HttpGet]
public ActionResult Page3(Patient patient)
{
// Pass patient again... to your next view
return View("~/Views/Page3.cshtml", patient);
}
Page3 View...
#using (Html.BeginForm("LOLOL", "PATIENT", null))
{
#Html.HiddenFor(model => model.Name) #* Keep name again for your next post *#
#Html.HiddenFor(model => model.Address) #* Now we are keeping the address as well *#
#Html.ValidationSummary(true)
<fieldset>
<legend>PATIENT</legend>
<div class="editor-label">
#Html.LabelFor(model => model.City)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.City)
#Html.ValidationMessageFor(model => model.City)
</div>
</fieldset>
<p>
<input type="submit" value="SUBMIT" />
</p>
}
And so on and so forth... until you have your model complete and want to do something with it.

BeginForm<TController> won't post model first time round

I have an issue using the latest mvc futures BeginForm extension. I have disabled clientsidevalidation as I have read that there may still be a bug with it. Basically the first time it gets posted the model is null, the second time (and any time after that) it will post the model fine.
It works if I switch it back to #using(Html.BeginForm()) or #using(Html.BeginForm("action", "controller" etc but I would rather make use of the strongly typed version. Here is my code:
Controller
[HandleError]
public class HomeController : BaseServiceController<IUserService>
{
public HomeController(IUserService service, IMapper mapper, ICustomPrincipal user)
: base(service, mapper, user)
{}
[AllowAnonymous]
[HttpGet]
public ActionResult Logon(string ReturnUrl)
{
LogonModel model = new LogonModel() { ReturnUrl = ReturnUrl };
return View(model);
}
[AllowAnonymous]
[HttpPost]
public ActionResult Logon(LogonModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
SfUser sfUser = service.Logon(model.UserName, model.Password);
if (sfUser == null)
{
ModelState.AddModelError("General", "Username or Password incorrect");
return View(model);
}
Response.Cookies.Add(TicketMaster.setAuthCookie(new CustomPrincipalSerializeModel(sfUser)));
return Redirect(model.ReturnUrl);
}
View
#model LogonModel
#{
ViewBag.Title = "Logon";
}
//#using(Html.BeginForm()) //Works
#using(Html.BeginForm<HomeController>(c => c.Logon(Model)))
{
#Html.HiddenFor(m => m.ReturnUrl)
<div class="editor-label">
#Html.LabelFor(m => m.UserName)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.UserName)
#Html.ValidationMessageFor(m => m.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.Password)
</div>
<div class="editor-field">
#Html.PasswordFor(m => m.Password)
#Html.ValidationMessageFor(m => m.Password)
</div>
<br />
<p>
<input type="submit" value="Login" /><br />
#Html.ValidationSummary(true)
</p>
}
I could understand the problem if it always posted a null model, but only on first post? Driving me mad.
I know this is old and probably already solved somehow, but you should pass null instead of Model to the expression. That way, the model will be built using the form values.
It took me a few hours to find this. Go to the Strong Typed Html BeginForm<TController> section. This is the key point:
You want to pass your values within the form’s scope, not the
BeginForm method itself.
In your case, just change
#using(Html.BeginForm<HomeController>(c => c.Logon(Model)))
to
#using(Html.BeginForm<HomeController>(c => c.Logon(null)))

Custom update method in MVC 4 with WCF

I am a newbie in MVC and currently using MVC 4 + EF Code First and WCF in my web project. Basically, in my project, WCF services will get the data from database for me, and it will take care of updating data as well. As a result, when I finish updating a record, I have to call the service client to make the change for me other than the "traditional" MVC way. Here is my sample code:
Model:
[DataContract]
public class Person
{
[Key]
[DataMember]
public int ID{ get; set; }
[DataMember]
public string Name{ get; set; }
[DataMember]
public string Gender{ get; set; }
[DataMember]
public DateTime Birthday{ get; set; }
}
Controller:
[HttpPost]
public ActionResult Detail(int ID, string name, string gender, DateTime birthday)
{
// get the WCF proxy
var personClient = personProxy.GetpersonSvcClient();
//update the info for a person based on ID, return true or false
var result = personClient.Updateperson(ID, name, gender, birthday);
if (result)
{
return RedirectToAction("Index");
}
else
{
//if failed, stay in the detail page of the person
return View();
}
}
View:
#model Domain.person
#{
ViewBag.Title = "Detail";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Detail</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Person</legend>
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Gender)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Gender)
#Html.ValidationMessageFor(model => model.Gender)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Birthday)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Birthday)
#Html.ValidationMessageFor(model => model.Birthday)
</div>
<p>
<input type="submit" value="Update"/>
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
The controller is the part I am confused of. The Detail function takes multiple parameters, how can I call it from the View? Also, what should I put into this return field in the controller:
//if failed, stay in the detail page of the person
return View();
We usually put the model in, but the model seems to be not changed, since I am updating the database directly from my WCF service.
Any suggestion would be really appreciated!
UPDATE:
I know I can probably get it works by change the update method to take only one parameter which is the model itself, but this is not an option in my project.
you call the Details action in the controller when you hit "Update"
//sidenote : use single parameter in your function that accepts the values it makes life easier
The form will call the post method in the controller that has the same name as the get method that rendered the view when it is submitted.
You can alter this default behavior by specifying parameters in the BeginForm method
#using (Html.BeginForm("SomeAction", "SomeController"))
Also, you are using a strongly typed view (good!), so you can change the signature of your post method to accept the model object
[HttpPost]
public ActionResult Detail(Person person)

How to turn off MVC form validation

I have a data-first set-up so my models are generated by the entity framework from my database and there is no default [Required] annotations. I have a simple table with three fields. One ID and two VARCHAR / text based fields.
No matter what I try, I cannot get the CRUD forms to stop validation. I disabled in the Web.config, I add [ValidateInput(false)] to the Create() method in the controller, but has no effect. I set the #Html.ValidationSummary to false,
This is the basic view:
#using (Html.BeginForm()) {
#Html.ValidationSummary(false)
<fieldset>
<legend>CallType</legend>
<div class="editor-label">
#Html.LabelFor(model => model.CALLTYPE)
</div>
<div class="editor-field">
#Html.TextBox("calltype", "", new { style = "width: 50px;" })
#Html.ValidationMessageFor(model => model.CALLTYPE)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DESCRIPTION)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DESCRIPTION)
#Html.ValidationMessageFor(model => model.DESCRIPTION)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Model (generated by Framework):
public partial class CALLTYPES2
{
public int ID { get; set; }
public string CALLTYPE { get; set; }
public string DESCRIPTION { get; set; }
}
Even if I insert just one character in each field, it still says: "The Value 'x' is invalid"
(I leave the validation messages on so I can see what is going on.)
What am I supposed to do? And how would I validate these fields later on - can I just add [Required] to Model generated code? What if I regenerate the Model from the database?
Does this have something to do with the model state in the controller?
[HttpPost]
public ActionResult Create(CALLTYPES2 calltype)
{
if (ModelState.IsValid)
{
db.CALLTYPES2.Add(calltype);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(calltype);
}
Not sure what I am missing and the tutorials I have read do not shed much light. Thanks for your response and apologies for my ignorance.
UPDATE
Found my error - The object name "calltype" in the Method Create() is the same as the name/id of the form field "calltype". I guess the binder tries to bind the string "calltype" to the object "calltype". Renamed it to:
public ActionResult Create(CALLTYPES2 ctype)
Now it works in both the Edit and Create Windows. "ctype" is not clashing with "calltype".
You forgot to include the ID field in your form. You could include it as a hidden field:
#Html.HiddenFor(model => model.ID)
Now the value of the ID property will be sent to the server when the form is submitted and the default model binder should not complain.

keeping Object Data after passing it to a strongly typed view

I have a simple ASP.NET MVC 3 site, from my Controller out of the Edit Action I pass a Object (a class which is also mapped by nhibernate)
After editing and clicking save i pass it to the [HTTPPost] decoraded Method but and all properties are correct, excerpt the "id" property it hat a guid value equivalent to NULL (00000000-0000-000...).
Is there a problem using the Domain Model to strongly type my Views? May the problem be that Id has:
{get; private set;}
???
Thanks in advance.
Here The Code:
My View:
'#model fnh.DataModel.Kunde
#{
View.Title = "EditKunde";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>EditKunde</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Fields</legend>
#Html.EditorFor(model => model._id)
#Html.EditorFor(model => model._KdNr);
<div class="editor-label">
#Html.LabelFor(model => model._Name)
`enter code here` </div>
<div class="editor-field">
#Html.EditorFor(model => model._Name)
#Html.ValidationMessageFor(model => model._Name)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
'
My Controller Actions:
' public ActionResult EditKunde(Guid id)
{
return View(_kunden.GetKundeById(id));
}
[HttpPost]
public ActionResult EditKunde(Kunde kunde)
{
Ansprechpartner anp = new Ansprechpartner();
anp._Name = "JustATry";
kunde._Ansprechpartner.Add(anp);
`enter code here` _kunden.EditKunde(kunde);
return View();
}'
Are you adding the Id property to your form (maybe as a hidden field)?
It would be useful if you post the Action code (for both Edit Actions), and the View.

Resources