Can i have more than one Post method in asp.net mvc controller? - asp.net-mvc

I have two views, one is CustomerDetail.cshtml and another is PAymentDetail.cshtml and i have one controller QuoteController.cs.
Both the Views has Submit buttons and HTTPPOST method for both the views are in QuoteController.cs.
[HttpPost]
public ActionResult CustomerDetail(FormCollection form)
{
}
[HttpPost]
public ActionResult PAymentDetail(FormCollection form)
{
}
Now, when i click on Submit button of Payment details, it is calling/routing to HttpPost method of CustomerDetail rather than PAymentDetail.
Could anyone help me on this?? What i'm doing wrong? Both The form method is POST.

For the PaymentDetail, you use this in the view:
#using(Html.BeginForm("PAymentDetail","Quote",FormMethod.Post))
{
//Form element here
}
The result html will be
<form action="/Quote/PAymentDetail" method="post"></form>
The same for customer Detail
#using(Html.BeginForm("CustomerDetail","Quote",FormMethod.Post))
{
//Form element here
}
Hope that help. Having two post methods in the same controller is not a problems, as long as these methods have different names.
For a better way other than FormCollection, I recommend this.
First, you create a model.
public class LoginModel
{
public string UserName { get; set; }
public string Password { get; set; }
public bool RememberMe { get; set; }
public string ReturnUrl { get; set; }
}
Then, in the view:
#model LoginModel
#using (Html.BeginForm()) {
<fieldset>
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserName)
//Insted of razor tag, you can create your own input, it must have the same name as the model property like below.
<input type="text" name="Username" id="Username"/>
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Password)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Password)
</div>
<div class="editor-label">
#Html.CheckBoxFor(m => m.RememberMe)
</div>
</fieldset>
}
These user input will be mapped into the controller.
[HttpPost]
public ActionResult Login(LoginModel model)
{
String username = model.Username;
//Other thing
}
Good luck with that.

Absolutely! Just ensure you are posting to the right action method, check your rendered HTML's form tags.
Also, the FormCollection isn't a good design for MVC.

If you want to have only one url, here is another approach: http://www.dotnetcurry.com/ShowArticle.aspx?ID=724
The idea is to use a form element (the button or a hidden element) to decide, which form has been submitted. Then you write a custom action selector (http://msdn.microsoft.com/en-us/library/system.web.mvc.actionmethodselectorattribute.aspx) which decides which action will be invoked.

Related

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.

ASP.NET MVC 3 Validation on Nested Objects not working as expected - validates child object twice and not parent object

I am trying to get ASP.NET MVC 3 to generate forms from complex, nested objects. There is one validation behaviour I found which was unexpected and I am not sure if it's a bug in the DefaultModelBinder or not.
If I have two objects, lets call the "parent" one "OuterObject", and it has a property of type "InnerObject" (the child):
public class OuterObject : IValidatableObject
{
[Required]
public string OuterObjectName { get; set; }
public InnerObject FirstInnerObject { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!string.IsNullOrWhiteSpace(OuterObjectName) && string.Equals(OuterObjectName, "test", StringComparison.CurrentCultureIgnoreCase))
{
yield return new ValidationResult("OuterObjectName must not be 'test'", new[] { "OuterObjectName" });
}
}
}
Here is InnerObject:
public class InnerObject : IValidatableObject
{
[Required]
public string InnerObjectName { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!string.IsNullOrWhiteSpace(InnerObjectName) && string.Equals(InnerObjectName, "test", StringComparison.CurrentCultureIgnoreCase))
{
yield return new ValidationResult("InnerObjectName must not be 'test'", new[] { "InnerObjectName" });
}
}
}
You will notice the validation I put on both.. just some dummy validation to say some value can't equal "test".
Here is the view that this will display in (Index.cshtml):
#model MvcNestedObjectTest.Models.OuterObject
#{
ViewBag.Title = "Home Page";
}
#using (Html.BeginForm()) {
<div>
<fieldset>
<legend>Using "For" Lambda</legend>
<div class="editor-label">
#Html.LabelFor(m => m.OuterObjectName)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.OuterObjectName)
#Html.ValidationMessageFor(m => m.OuterObjectName)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.FirstInnerObject.InnerObjectName)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.FirstInnerObject.InnerObjectName)
#Html.ValidationMessageFor(m => m.FirstInnerObject.InnerObjectName)
</div>
<p>
<input type="submit" value="Test Submit" />
</p>
</fieldset>
</div>
}
..and finally here is the HomeController:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new OuterObject();
model.FirstInnerObject = new InnerObject();
return View(model);
}
[HttpPost]
public ActionResult Index(OuterObject model)
{
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
return View(model);
}
}
What you will find is that when the model gets validated by the DefaultModelBinder, the "Validate" method in "InnerObject" gets hit twice, but the "Validate" method in "OuterObject" does not get hit at all.
If you take off IValidatableObject from "InnerObject", then the one on "OuterObject" will get hit.
Is this a bug, or should I expect it to work that way? If I should expect it to, what's the best workaround?
This answer is just to provide one workaround I have just thought of - so it is not really an answer! I am still not sure if this is a bug or what the best workaround is, but here is one option.
If you remove the custom validation logic from "InnerObject" and incorporate it into "OuterObject" it seems to work fine. So basically this works around the bug by only allowing the top-most object to have any custom validation.
Here is the new InnerObject:
//NOTE: have taken IValidatableObject off as this causes the issue - we must remember to validate it manually in the "Parent"!
public class InnerObject //: IValidatableObject
{
[Required]
public string InnerObjectName { get; set; }
}
And here is the new OuterObject (with the Validation code stolen from InnerObject):
public class OuterObject : IValidatableObject
{
[Required]
public string OuterObjectName { get; set; }
public InnerObject FirstInnerObject { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!string.IsNullOrWhiteSpace(OuterObjectName) && string.Equals(OuterObjectName, "test", StringComparison.CurrentCultureIgnoreCase))
{
yield return new ValidationResult("OuterObjectName must not be 'test'", new[] { "OuterObjectName" });
}
if (FirstInnerObject != null)
{
if (!string.IsNullOrWhiteSpace(FirstInnerObject.InnerObjectName) &&
string.Equals(FirstInnerObject.InnerObjectName, "test", StringComparison.CurrentCultureIgnoreCase))
{
yield return new ValidationResult("InnerObjectName must not be 'test'", new[] { "FirstInnerObject.InnerObjectName" });
}
}
}
}
This works as I would expect, hooking up the validation error to each field correctly.
It is not a great solution because if I need to nest "InnerObject" in some other class, it does not share that validation - I need to replicate it. Obviously I could have a method on the class to store the logic, but each "parent" class needs to remember to "Validate" the child class.
I am not sure this is a problem with MVC 4 anymore, but...
If you use partial views made just for your InnerObjects, they will validate correctly.
<fieldset>
<legend>Using "For" Lambda</legend>
<div class="editor-label">
#Html.LabelFor(m => m.OuterObjectName)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.OuterObjectName)
#Html.ValidationMessageFor(m => m.OuterObjectName)
</div>
#Html.Partial("_InnerObject", Model.InnerObject)
<p>
<input type="submit" value="Test Submit" />
</p>
</fieldset>
Then add this partial "_InnerObject.cshtml":
#model InnerObject
<div class="editor-label">
#Html.LabelFor(m => m.InnerObjectName)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.InnerObjectName)
#Html.ValidationMessageFor(m => m.InnerObjectName)
</div>
Should you have made OuterObject base class for InnerObject instead of creating a relationship as you did? (Or vice versa) and provide the view the base object as the ViewModel?
This will mean that when model binding the default constructor of the OuterObject (or which ever class is your base) will be called indirectly invoking Validate on both objects.
i.e.
Class:
public class OuterObject : InnerObject, IValidateableObject
{
...
}
View:
#model MvcNestedObjectTest.Models.OuterObject
Controller Action:
public ActionResult Index(OuterObject model)

asp.net mvc scaffolding and GUID as the primary key

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.

Polymorphism and Strongly-typed Views in ASP.NET MVC

I have a problem where I have two forms that are identical except that the required fields are different. For example, let's say the forms have the same fields: X, Y, and Z. In Form #1, X is required, but in Form #2, Y is required.
So I created two view models, Form1 and Form2, with the same properties but with the Required attributes on different properties. I then created an interface, let's call it IForm, that both models implement and built a View that is strongly typed on IForm.
The problem with that solution is that ASP.NET MVC 3 reads the attributes on IForm instead of the dynamic type of the object being passed to the view, that is Form1 or Form2, so I don't get the client side JavaScript field validation that I want.
I'm wondering if there's a solution other than creating a strongly-typed View for each view model.
I have put together a sample with what you described (I think) and I'm able to get it to work:
public class TestController : Controller
{
public ActionResult Foo()
{
return View("IFoo");
}
[HttpPost]
public ActionResult Foo(Foo foo)
{
if (!ModelState.IsValid)
return View("IFoo", foo);
return RedirectToAction("Foo");
}
public ActionResult Bar()
{
return View("IFoo");
}
[HttpPost]
public ActionResult Bar(Bar bar)
{
if (!ModelState.IsValid)
return View("IFoo", bar);
return RedirectToAction("Bar");
}
}
// The Interface - the Required attributes are not
// on the interface, just the concrete classes
public interface IFoo
{
string Name { get; set; }
string Description { get; set; }
}
// Concrete Class 1 - Name is required
public class Foo : IFoo
{
[Required(ErrorMessage="Name is required.")]
public string Name { get; set; }
public string Description { get; set; }
}
// Concrete Class 2 - Description is required
public class Bar : IFoo
{
public string Name { get; set; }
[Required(ErrorMessage = "Description is required.")]
public string Description { get; set; }
}
I then defined a strongly typed view:
#model Test.Controllers.IFoo
<h2>Test</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>IFoo</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>
<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>
}
When I browse to /test/foo and hit the Save, I get a validation error on Name.
When I browse to /test/bar and hit the Save, I get a validation error on Description.
Try partial form validation approach.
http://softwaredevelopmentsolutions.blogspot.com/2011/06/aspnet-mvc-3-partial-form-validation-on.html
Create custom action filter attribute. Decorate the action methods with it to ignore validation properties according to the forms.

Resources