ASP.NET code snippit to query database for unique field value - asp.net

I'm trying to create custom remote data annotation to check for unique values.
So far I have:
[Remote("checkForUniqueSpeciesName", "Create", ErrorMessage = "A Species by that name already exists.")]
public string SpeciesName { get; set; }
and
public ActionResult checkForUniqueSpeciesName(string species_name)
{
bool is_unique = ........
return Json(is_unique, JsonRequestBehavior.AllowGet);
}
To be honest, I don't really understand how this works, I'm just trying to follow examples found on the web. I guess checkForUniqueSpeciesName is called when the form is submitted, and the method returns true or false. Is there something I need to put in the view to make the validation message come up, such as?
#Html.ValidationMessageFor(model => model.SpeciesName, "", new { #class = "text-danger" })
Do I need that?
Model Species.cs:
public class Species
{
[Key]
public int SpeciesId { get; set; }
[Display(Name = "Species")]
[Required(ErrorMessage = "You must enter a species name.")]
[Remote("CheckForUniqueSpeciesName", "Create", ErrorMessage = "A Species by that name already exists.")]
public string SpeciesName { get; set; }
}
Controller SpeciesController.cs:
namespace Gators3.Controllers
{
public class SpeciesController : Controller
{
private GatorsContext db = new GatorsContext();
// GET: Species
public ActionResult Index()
{
return View(db.Species.ToList());
}
// GET: Species/Create
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "SpeciesId,SpeciesName")] Species species)
{
if (ModelState.IsValid)
{
db.Species.Add(species);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(species);
}
public ActionResult CheckForUniqueSpeciesName(string speciesName)
{
using (GatorsContext ctx = new GatorsContext())
{
bool isUnique = !ctx.Species.Any(s => s.SpeciesName == speciesName);
return Json(isUnique, JsonRequestBehavior.AllowGet);
}
}
.
.
.
.
View Views->Species->Create.cshtml:
#model Gators3.Models.Species
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Species</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.SpeciesName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SpeciesName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SpeciesName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}

I guess checkForUniqueSpeciesName is called when the form is
submitted, and the method returns true or false.
No, that is not the case. The [RemoteAttribute] adds some JavaScript to your page automatically that will call a method on your Controller to do some server side validation and display the result on the page without the user needing to submit the whole HTML form. i.e. The validation is invoked when you tab out of the text box, not when you click submit.
With your code, I assume your controller is named CreateController?
I'm guessing you're just missing your data access code to actually check uniqueness?
So something like this would be required:
public ActionResult CheckForUniqueSpeciesName(string speciesName)
{
using (YourEntityFrameworkDbContext ctx = new YourEntityFrameworkDbContext())
{
bool isUnique = !ctx.Species.Any(s => s.SpeciesName == speciesName);
return Json(isUnique , JsonRequestBehavior.AllowGet);
}
}
Then in your view, you just need something like this:
#Html.ValidationMessageFor(x => x.SpeciesName)
Which will display the validation message you specified in your [Remote] attribute.
By the way, just as a side note - the coding conventions/casing you've applied to some of your code won't be popular with most C# programmers (unless your team are abiding by an unusual standard) so note the formatting I've applied.
Update - I think your code needs to have the following:
[Remote("CheckForUniqueSpeciesName", "Species", ErrorMessage="A Species by that name already exists.")]

Related

ASP.NET List<SelectListItem> is passing null to controller

I have a form, with radiobuttons. After selecting one of them, in [POST] method, the 'Companies' List Count is still 0. Do you have any idea what causes the problem?
Controller:
// GET: Commission/Create
public ActionResult Create()
{
CommissionMVCCreateModel commission = new CommissionMVCCreateModel();
List<CompanyMVCModel> companies = SQLiteDataAccess.LoadCompanies();
commission.Companies = companies.Select(x => new SelectListItem { Text = x.CompanyName, Value = x.Id.ToString() }).ToList();
return View(commission);
}
// POST: Commission/Create
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Create(CommissionMVCCreateModel commissionModel)
{
try
{
if(ModelState.IsValid)
{
}
// TODO: Add insert logic here
return View();
}
catch
{
return View();
}
}
Model:
public class CommissionMVCCreateModel
{
public List<SelectListItem> Companies { get; set; } = new List<SelectListItem>();
...
...
}
View:
<form>
...
...
#Html.LabelFor(model => model.Companies, htmlAttributes: new { #class = "control-label" })
<div class="btn-group-vertical">
#foreach (var company in Model.Companies)
{
<label class="btn btn-primary col-md-offset-2 col-md-5 ">
<input type="radio" name="company" id="#company.Value" value="#company.Value"/>
#company.Text
</label>
}
</div>
...
...
</form>
In the form i have also part for creating new Company, and it's values are passed correctly. The only problem i have is with this SelectListItem. I tried also using SelectList, but the result was the same.

Validation failed for one or more entities. See 'EntityValidationErrors' property for more details. ASP.NET MVC

I have encountered the error shown as my title. I have tried to search for solutions but all I got is solution about using try catch code blocks.
I have been using a course documentation that I have made to guide me on doing this project but the error that I have encountered this time, I am clueless about which part has gone wrong and how to check the wrong part.
There are two parts that I have commented it with // strange comments which means that I have no idea is it where the error occur or something like that.
Thanks for reading my question.
This is my PetRescued Model
public class PetRescued
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string PetName { get; set; }
public int PetAge { get; set; }
[Required]
[StringLength(6)]
public string PetGender { get; set; }
public short PetWeightInKg { get; set; }
public DateTime DateWhenRescued { get; set; }
public PetSpecies PetSpecies { get; set; }
public byte PetSpeciesId { get; set; }
}
This is my PetRescued Controller
public ActionResult New() //populate form
{
var petspecies = _context.PetSpecieses.ToList();
var viewModel = new PetRescuedViewModel
{
PetSpecies = petspecies
};
return View("PetRescued", viewModel);
}
[HttpPost]
public ActionResult Save(PetRescued petRescued)
{
if (petRescued.Id == 0)
_context.PetRescueds.Add(petRescued);
else
{
var petRescuedInDb = _context.PetRescueds.Single(c => c.Id == petRescued.Id);
petRescuedInDb.PetName = petRescued.PetName;
petRescuedInDb.PetAge = petRescued.PetAge;
petRescuedInDb.PetGender = petRescued.PetGender;
petRescuedInDb.PetWeightInKg = petRescued.PetWeightInKg;
petRescuedInDb.PetSpeciesId = petRescued.PetSpeciesId; //strange
petRescuedInDb.DateWhenRescued = petRescued.DateWhenRescued;
}
_context.SaveChanges();
return RedirectToAction("Index", "PetRescued");
}
This is my PetRescued ViewModel
public class PetRescuedViewModel
{
public IEnumerable<PetSpecies> PetSpecies { get; set; }
public PetRescued PetRescueds { get; set; }
public PetRescuedViewModel()
{
PetRescueds = new PetRescued();
}
}
This is my PetRescued Form
#using (Html.BeginForm("Save", "PetRescued"))
{
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.PetName)
#Html.TextBoxFor(m => m.PetRescueds.PetName, new { #class = "form-control" })
</div>
//strange
<div class="form-group">
#Html.LabelFor(m => m.PetSpecies)
#Html.DropDownListFor(m => m.PetRescueds.PetSpeciesId, new SelectList(Model.PetSpecies, "Id", "SpeciesName"), "Select A Species", new {#class = "form-control"})
</div>
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.PetAge)
#Html.TextBoxFor(m => m.PetRescueds.PetAge, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.PetGender)
#Html.TextBoxFor(m => m.PetRescueds.PetGender, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.PetWeightInKg)
#Html.TextBoxFor(m => m.PetRescueds.PetWeightInKg, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.DateWhenRescued)
#Html.TextBoxFor(m => m.PetRescueds.DateWhenRescued, "{0:d MMM yyyy}", new { #class = "form-control" })
</div>
#Html.HiddenFor(m => m.PetRescueds.Id)
<button type="submit" class="btn btn-primary">Save</button>
}
Look at your model definition
// This means this value is required
// and should not be greater than 255 characters
[Required]
[StringLength(255)]
public string PetName { get; set; }
// This means this value is required
// and should not be greater than 6 characters
[Required]
[StringLength(6)]
public string PetGender { get; set; }
So either you are not sending a value from your client app or it is larger than the restrictions you stated.
Change your action method to this to validate your model in your backend (you should never trust the client input)
[HttpPost]
public ActionResult Save(PetRescued petRescued)
{
if (ModelState.IsValid) // Check for errors
{
if (petRescued.Id == 0)
_context.PetRescueds.Add(petRescued);
else
{
var petRescuedInDb = _context.PetRescueds.Single(c => c.Id == petRescued.Id);
petRescuedInDb.PetName = petRescued.PetName;
petRescuedInDb.PetAge = petRescued.PetAge;
petRescuedInDb.PetGender = petRescued.PetGender;
petRescuedInDb.PetWeightInKg = petRescued.PetWeightInKg;
petRescuedInDb.PetSpeciesId = petRescued.PetSpeciesId; //strange
petRescuedInDb.DateWhenRescued = petRescued.DateWhenRescued;
}
_context.SaveChanges();
return RedirectToAction("Index", "PetRescued");
}
else
return View(petRescued); // Return the same view with the original data
// or with the correct model of your view, at least
}
UPDATE
Correct your view model to reflect your correct data. That means, make sure you are sending the correct model to the backend. ASP.Net MVC has something called Model Binding, which is the mechanism used to convert the data received from the client into your C# model. By default, it works by detecting the name of the values passed from the client and finding an exact mapping with the properties of the model. That means that in your view you are declaring this
#Html.TextBoxFor(m => m.PetRescueds.PetName, new { #class = "form-control" })
So, if you inspect the data sent by the browser you will see that the form data includes something like
PetRescueds.PetAge: whatever_the_client_typed
That will not be mapped to your model, because your model doesn't have a property named PetRescueds with a subproperty named PetName, your action model is directly a PetRescued model. So either change your view by specifying directly the name attr like this
#Html.TextBox("PetName", Model.PetRescueds.PetName, new { #class = "form-control" })
Or change your action model to reflect your view model definition. Either way, your view model should be consistent through your action and view. Otherwise, you will end up receiving null values in your action model in spite of filling them correctly on your view, or showing empty values in your views regardless of what you actually created on your controller action.
So, basically, check your model definitions. Make sure you are using a correct model definition to display in your views. Make sure your view is correctly defined as to what you are expecting to receive in your backend controller.
Then, change your view to include validation errors retrieved from the server
#using (Html.BeginForm("Save", "PetRescued"))
{
<!-- This will show your errors-->
#Html.ValidationSummary()
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.PetName)
<!-- Or you can show errors for each model property -->
<!-- like this -->
#Html.ValidationMessageFor(m => m.PetRescueds.PetName);
#Html.TextBox("PetName", Model.PetRescueds.PetName, new { #class = "form-control" })
</div>
//strange
<div class="form-group">
#Html.LabelFor(m => m.PetSpecies)
#Html.DropDownListFor(m => m.PetRescueds.PetSpeciesId, new SelectList(Model.PetSpecies, "Id", "SpeciesName"), "Select A Species", new {#class = "form-control"})
</div>
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.PetAge)
#Html.TextBoxFor(m => m.PetRescueds.PetAge, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.PetGender)
#Html.TextBoxFor(m => m.PetRescueds.PetGender, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.PetWeightInKg)
#Html.TextBoxFor(m => m.PetRescueds.PetWeightInKg, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.DateWhenRescued)
#Html.TextBoxFor(m => m.PetRescueds.DateWhenRescued, "{0:d MMM yyyy}", new { #class = "form-control" })
</div>
#Html.HiddenFor(m => m.PetRescueds.Id)
<button type="submit" class="btn btn-primary">Save</button>
}
You can read more about data validation at Microsofts's
Let's try and fix this.
First, let's change your controller to be able to do something with the errors returned by the model binder.
[HttpGet]
public ActionResult New() //populate form
{
var petspecies = _context.PetSpecieses.ToList();
var viewModel = new PetRescuedViewModel
{
PetSpecies = petspecies
};
return View("PetRescued", viewModel);
}
[HttpPost]
public ActionResult Save(PetRescuedViewModel viewModel)
{
if (ModelState.IsValid) // Check for errors
{
if (petRescued.Id == 0)
_context.PetRescueds.Add(petRescued);
else
{
var petRescuedInDb = _context.PetRescueds.Single(c => c.Id == petRescued.Id);
petRescuedInDb.PetName = viewModel.PetRescued.PetName;
petRescuedInDb.PetAge = viewModel.PetRescued.PetAge;
petRescuedInDb.PetGender = viewModel.PetRescued.PetGender;
petRescuedInDb.PetWeightInKg = viewModel.PetRescued.PetWeightInKg;
petRescuedInDb.PetSpeciesId = viewModel.PetRescued.PetSpeciesId; //strange
petRescuedInDb.DateWhenRescued = viewModel.PetRescued.DateWhenRescued;
}
_context.SaveChanges();
return RedirectToAction("Index", "PetRescued");
}
viewModel.PetSpecies = _context.PetSpecieses.ToList(); // populate the list again as the contents are lost when the form is submitted.
return View("PetRescued", viewModel); // validation errors found, so redisplay the same view
}
Then, change your view to display the errors. We're basically doing what this answer suggests.
#using (Html.BeginForm("Save", "PetRescued"))
{
// Displays a summary of all the errors.
#Html.ValidationSummary()
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.PetName)
#Html.TextBoxFor(m => m.PetRescueds.PetName, new { #class = "form-control" })
// Or you can add this to each property
#Html.ValidationMessageFor(m => m.PetRescueds.PetName)
</div>
//strange
<div class="form-group">
#Html.LabelFor(m => m.PetSpecies)
#Html.DropDownListFor(m => m.PetRescueds.PetSpeciesId, new SelectList(Model.PetSpecies, "Id", "SpeciesName"), "Select A Species", new {#class = "form-control"})
</div>
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.PetAge)
#Html.TextBoxFor(m => m.PetRescueds.PetAge, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.PetGender)
#Html.TextBoxFor(m => m.PetRescueds.PetGender, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.PetWeightInKg)
#Html.TextBoxFor(m => m.PetRescueds.PetWeightInKg, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.PetRescueds.DateWhenRescued)
#Html.TextBoxFor(m => m.PetRescueds.DateWhenRescued, "{0:d MMM yyyy}", new { #class = "form-control" })
</div>
#Html.HiddenFor(m => m.PetRescueds.Id)
<button type="submit" class="btn btn-primary">Save</button>
}
The above changes will at least give you which properties are having the problem.
The next step would be to fix the actual problem. If you do the above and can't figure it out further let me know which properties it is and I'll take a look.
I'm guessing it is public byte PetSpeciesId { get; set; } but let's see.
Hope this helps.
You should use the try and catch method to see which fields cause the 'EntityValidationErrors' :
ActionResult Save =>
try
{
_context.SaveChanges();;
}
catch (DbEntityValidationException ex)
{
var sb = new StringBuilder();
foreach (var failure in ex.EntityValidationErrors)
{
sb.AppendFormat("{0} failed validation\n", failure.Entry.Entity.GetType());
foreach (var error in failure.ValidationErrors)
{
sb.AppendFormat("- {0} : {1}", error.PropertyName, error.ErrorMessage);
sb.AppendLine();
}
}
throw new DbEntityValidationException(
"Entity Validation Failed - errors follow:\n" +
sb.ToString(), ex
);
}
You will know then which records do the exception.

How to incorporate another controller's view and behavior into "this" controller's view?

I have a jQueryUI tabbed html page, and in its content area for one of the tabs, I have put as follows:
<div id="tabs-1ua">
#RenderPage("~/Views/Admin/Create.cshtml")
</div>
The Create.cshtml page does correctly appear within my tab, however when I create the user (this view is a basic user creation page) and click the button, nothing happens. No user is created and no error is presented. The "this" html with the tabs is in a different controller which does not have any model associations. The user creation is inside the AdminController, pertinent methods shown below:
public ActionResult Create()
{
return View();
}
[HttpPost]
public async Task<ActionResult> Create(CreateModel model)
{
if (ModelState.IsValid)
{
AppUser user = new AppUser { UserName = model.Name, Email = model.Email};
IdentityResult result = await UserManager.CreateAsync(user,
model.Password);
if (result.Succeeded)
{
return RedirectToAction("Index");
}
else
{
AddErrorsFromResult(result);
}
}
return View(model);
}
I put a breakpoint at the beginning of the Post method, but it was never hit when I accessed the create page from within my other page.
When I access this page directly and create a user, I get the expected behavior for new creation and validation. The model is as follows:
public class CreateModel
{
[Required]
public string Name { get; set; }
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
And the Create.cshtml view is as follows:
#model IdentityDevelopment.Models.CreateModel
#{ ViewBag.Title = "Create User";}
<h2>Create User</h2>
#Html.ValidationSummary(false)
#using (Html.BeginForm())
{
<div class="form-group">
<label>Name</label>
#Html.TextBoxFor(x => x.Name, new { #class = "form-control" })
</div>
<div class="form-group">
<label>Email</label>
#Html.TextBoxFor(x => x.Email, new { #class = "form-control" })
</div>
<div class="form-group">
<label>Password</label>
#Html.PasswordFor(x => x.Password, new { #class = "form-control" })
</div>
<button type="submit" class="btn btn-primary">Create</button>
#Html.ActionLink("Cancel", "Index", null, new { #class = "btn btn-default" })
}
My questions are, is it possible to do what I am trying to do? If so what changes do I need to make in order to reuse the existing available code?
Thank you.
You may explcitly specify which action method the form should post to when submit button is clicked.
You can use this overload of Html.BeginForm method to do so.
public static MvcForm BeginForm(
this HtmlHelper htmlHelper,
string actionName,
string controllerName
)
So update your Create view.
#model IdentityDevelopment.Models.CreateModel
#using (Html.BeginForm("Create","Admin"))
{
#Html.TextBoxFor(x => x.Name, new { #class = "form-control" })
<button type="submit" class="btn btn-primary">Create</button>
}
Now nomatter where you include this view, it will always post to Admin/Create
You should move your create form into a partial view which you can then render with RenderPartial. Then in your parent html page form do an ajax post to the partial views controller that defines the create method. Then you can use your partial anywhere you like with the logic centralized into the partial views controller.

Passing multiple models from one view to a controller

If I google for "multiple models in one view" I can only find results about how I can pass models to a view. But I'm interested in the "from view to controller" direction.
So let's assume:
I have 3 different forms and 1 table (WebGrid) in one view.
And I have one model per form and one model for the table.
Let my model classes be ModelF1, ModelF2, ModelF3 and ModelT.
All the examples I have seen until now uses a container ViewModel like
class MyViewModel {
ModelF1 inst1,
ModelF2 inst2,
ModelF3 inst3,
ModelT instT
}
And then they pass it between view <-> controller in 2 ways.
But I want to catch my models this way without using a viewmodel:
class MyController {
ActionResult Index() {
return new View(modelF1Instance, modelF2Instance, modelF3Instance, modelTInstance);
}
ActionResult Form1Action(ModelF1 inst1, ModelT instT) {
// process models after a form1 submit
}
ActionResult Form2Action(ModelF2 inst2, ModelT instT) {
// process models after a form2 submit
}
ActionResult Form3Action(ModelF3 inst3, ModelT instT) {
// process models after a form3 submit
}
}
Is this possible without parsing the whole form elements in a CustomBinder?
Firstly you can only send a strongly typed view model back to your view using
return View(model);
View is a method on the base class, not a class to be instantiated with return new View(...
Then to your real question: Yes you can do this, but using a top level ViewModel which contains your different form items is far, far easier in the majority of use cases. The main problem the top level container ViewModel handles really well is value persistence and server-side validation and error messages between round trips.
If you are only worried about the perception of inefficiency from creating a top level ViewModel container, then don't. This is far more efficient than all the workarounds you may have to put in place in order to get well behaved forms working without the top level ViewModel.
There is some example code below. The code below should demonstrate that using models contained within the top level ViewModel is just simpler and neater: some of the forms deliberately don't round trip some of the state. Note the usage of HiddenFor and ModelState.Clear which are both related to what you are trying to do, but even these won't persist the value for inst4.Name for Form4Submit. The various options explored are:
Use a query parameter to denote which form is being posted
Use a different form name, but still with the view model.
Use a redirect-only Action for the form (send new instances, and only part of the viewmodel)
Use a mixture of the above
public class TestController : Controller
{
//
// GET: /Test/
[System.Web.Mvc.HttpGet]
public ActionResult Index(string msg = null)
{
var model = new MyViewModel
{
Inst1 = new ModelF1 { Name = "Name of F1" },
Inst2 = new ModelF2 (),
InstT = new ModelT {Name = "Name of T"},
PostNumber = 0,
Message = msg
};
return View(model);
}
[System.Web.Mvc.HttpPost]
public ActionResult Index(MyViewModel model, int option = 1)
{
// process models after a form1/2 submit
model.Message = "You posted " +
((option == 1) ? model.Inst1.Name : model.Inst2.Name)
+ " to Index for "
+ ((option == 1) ? "inst1" : "inst2");
model.PostNumber ++;
// This, and the hiddenFor are required to allow us to update the PostNumber each time
ModelState.Clear();
return View(model);
}
[System.Web.Mvc.HttpPost]
public ActionResult Form2Submit(MyViewModel model)
{
// process models after a form2 submit
model.Message = "You posted " + model.Inst2.Name + " to Form2Submit";
model.PostNumber++;
ModelState.Clear();
return View("Index", model);
}
[System.Web.Mvc.HttpPost]
public ActionResult Form3Submit(ModelF3 inst3, ModelT instT)
{
// process models after a form3 submit
var n = instT.Name;
var msg = "You posted " + inst3.Name + ", " + n + " to Form3Submit";
// We no longer have access to pass information back to the view, so lets redirect
return RedirectToAction("Index", new { msg = msg });
}
[System.Web.Mvc.HttpPost]
public ActionResult Form4Submit(ModelF4 inst4, MyViewModel model)
{
// process models after a form4 submit
var n = model.InstT.Name;
model.Message = "You posted " + inst4.Name + ", " + n + " to Form4Submit";
model.PostNumber++;
ModelState.Clear();
return View("Index", model);
}
public class MyViewModel
{
public int PostNumber { get; set; }
public string Message { get; set; }
public ModelF1 Inst1 { get; set; }
public ModelF2 Inst2 { get; set; }
public ModelT InstT { get; set; }
}
public class ModelBase { public string Name { get; set; } }
public class ModelF1 : ModelBase {}
public class ModelF2 : ModelBase { }
public class ModelF3 : ModelBase { }
public class ModelF4 : ModelBase { }
public class ModelT : ModelBase { }
}
Then for the multi-form view:
#using MyWebSite.Controllers;
#model TestController.MyViewModel
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<p>
#Html.Raw(Model.PostNumber) : #Html.Raw(Model.Message)
</p>
<p>
#Html.LabelFor(m => Model.InstT) : <br />
#Html.DisplayFor(m => Model.InstT)
</p>
<div>
<p>Default form submit</p>
#using (Html.BeginForm())
{
<div>
#Html.HiddenFor(m => m.PostNumber)
#Html.LabelFor(m => Model.Inst1.Name)
#Html.TextBoxFor(m => Model.Inst1.Name)
</div>
<input type="submit" value="Submit Index" />
}
</div>
<div>
<p>Use a parameter to denote the form being posted</p>
#using (Html.BeginForm("Index", "Test", new { option = 2 }))
{
<div>
#* Omitting these will not persist them between trips
#Html.HiddenFor(m => Model.Inst1.Name)
#Html.HiddenFor(m => Model.InstT.Name)*#
#Html.HiddenFor(m => m.PostNumber)
#Html.LabelFor(m => Model.Inst2.Name)
#Html.TextBoxFor(m => Model.Inst2.Name)
</div>
<input type="submit" value="Submit with option parameter" />
}
</div>
<div>
<p>Use a different form name, but still use the ViewModel</p>
#using (Html.BeginForm("Form2Submit", "Test"))
{
<div>
#Html.HiddenFor(m => Model.Inst1.Name)
#Html.HiddenFor(m => Model.InstT.Name)
#Html.HiddenFor(m => m.PostNumber)
#Html.LabelFor(m => Model.Inst2.Name)
#Html.TextBoxFor(m => Model.Inst2.Name)
</div>
<input type="submit" value="Submit F2" />
}
</div>
<div>
<p>Submit with a redirect, and no ViewModel usage.</p>
#using (Html.BeginForm("Form3Submit", "Test"))
{
var inst3 = new TestController.ModelF3();
<div>
#Html.HiddenFor(m => Model.InstT.Name)
#Html.LabelFor(m => inst3.Name)
#Html.TextBoxFor(m => inst3.Name)
</div>
<input type="submit" value="Submit F3" />
}
</div>
<div>
<p>Submit with a new class, and the ViewModel as well.</p>
#using (Html.BeginForm("Form4Submit", "Test"))
{
var inst4 = new TestController.ModelF4();
<div>
#Html.HiddenFor(m => Model.Message)
#Html.HiddenFor(m => Model.PostNumber)
#Html.HiddenFor(m => Model.Inst1.Name)
#Html.HiddenFor(m => Model.Inst2.Name)
#Html.HiddenFor(m => Model.InstT.Name)
#Html.LabelFor(m => inst4.Name)
#Html.TextBoxFor(m => inst4.Name)
</div>
<input type="submit" value="Submit F4" />
}
</div>
</body>
</html>

ASP.NET MVC redirect actions error with 'create' partial view on 'index' page

I'm having a problem with partial views. I have an index view of Announcements and I'm trying to add a partial view to create a new Announcement within the same page.
I can display the partial view, and submit the form to create a new record. The record gets submitted into the database, but when re-rendering the page, I get the error: Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper', {"Child actions are not allowed to perform redirect actions."} on my Html.Action statement in my index page.
I've been struggling to make this work, and have firstly changed the Html.Partial to a Html.Action statement as the controller methods weren't firing, then secondly, after I read that this error is because while rendering the page, .NET doesn't know what my redirect action is doing so automatically stops it, tried changing the Html.Action to Html.RedirectAction inside a code block, but still get the same error detailed above.
My model is quite simple:
public class Announcement
{
public Announcement()
{
AnnouncementDate = System.DateTime.Now;
}
[Key]
public int AnnouncementID { get; set; }
public string Title { get; set; }
public string Type { get; set; }
}
My Controller methods:
public ViewResult Index(string searchString, int? page)
{
var Announcements = from a in db.Announcements
select a;
if (!String.IsNullOrEmpty(searchString))
{
Announcements = Announcements.Where(s => (s.Title.ToUpper().Contains(searchString.ToUpper()) || s.AnnouncementText.ToUpper().Contains(searchString.ToUpper())));
}
Announcements = Announcements.OrderBy(s => s.Title);
int pageSize = 10;
int pageNumber = (page ?? 1);
return View(Announcements.ToPagedList(pageNumber, pageSize));
}
//
// GET: /Announcement/Create
public ActionResult Create()
{
Announcement announcement = new Announcement();
return PartialView(announcement);
}
//
// POST: /Announcement/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Announcement announcement)
{
if (ModelState.IsValid)
{
db.Announcements.Add(announcement);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(announcement);
}
Index.cshtml
#model PagedList.IPagedList<Project.Models.Announcement>
#using PagedList.Mvc;
#using PagedList;
#using (Html.BeginForm())
{
#Html.TextBox("SearchString", ViewBag.CurrentFilter as string, new { #class = "search-query", placeholder = "Search by name" })
<input type="submit" value="Search" class="btn" />
}
#item.Title
#item.Type
#Html.Action("Create"); // This is the line causing errors after I submit the Create form. Have tried changing to Html.RedirectAction
Create.cshtml:
#model Project.Models.Announcement
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(model => model.Title, new { #style = "width:250px" })
#Html.TextBoxFor(model => model.Type, new { #style = "width:250px" })
<input type="submit" value="Create" class="btn btn-small" />
}
After doing some testing locally...
You can keep
#Html.Action("Create")
However, you have to change one small thing. Define what action the POST points to in your form :)
#model Project.Models.Announcement
#using (Html.BeginForm("Create"))
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(model => model.Title, new { #style = "width:250px" })
#Html.TextBoxFor(model => model.Type, new { #style = "width:250px" })
<input type="submit" value="Create" class="btn btn-small" />
}

Resources