Use same partial to add form to Create and Edit actions - asp.net-mvc

When you create a new scaffolded item on VS it creates the views for Create and Edit actions that are almost identical, with the exception of the Edit view having an #Html.HiddenFor for your primary key.
Example of Edit view:
#model MyApp.Models.Destaque
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(m => m.IdDestaque)
<div class="form-group">
#Html.LabelFor(model => model.Mensagem, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Mensagem, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Mensagem, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.CaminhoImagem, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.CaminhoImagem, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CaminhoImagem, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UrlLink, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.UrlLink, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.UrlLink, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Salvar" class="btn btn-primary" />
</div>
</div>
</div>
}
If I place all content from the BeginForm (including the #using (...) and keep the #Html.HiddenFor(m => m.IdDestaque) in a _Form partial, it doesn't allow me to create new rows.
If I remove the #Html.HiddenFor from the _Form partial, the Edit action does not work (ModelState is invalid).
So what is the right way to do this and keep the DRY principles? Removing the PK from the ModelState validation in my Edit action seems an "uggly" workaround.

You should render the id of your model inside the view and use another overload for Html.BeginForm as follows:
#using (Html.BeginForm("Edit", "Controller", FormMethod.Post, new{attr1 = value1, attr2 = value2}))
{
#Html.HiddenFor(x => x.Id)
...
}
You always make a post to the EditAction. In you controller you will then only need one POST action for Edit and two get actions one Create and other Edit.
public ActionResult Create()
{
return View("Edit"); //return the edit view
}
public ActionResult Edit(int id)
{
var entity = manager.FetchById(id);
if (entity == null)
return HttpNotFound();
//convert your entity to model (optional, but recommended)
var model = Mapper.Map<ViewModel>(entity);
//return your edit view with your model
return View(model);
}
[HttpPost]
public ActionResult Edit(ViewModel model)
{
if (ModelState.IsValid)
{
//work with your model
return RedirectToAction("Index", "Controller");
}
//at this point there is an error, so your must return your view
return View(model);
}
Hope this helps!

Related

How to fix submit form call http method get with query params

I have an asp net core mvc application. An action that create article. The problem is that when I submit the form , my application always calls the get method. How to fix this ?
Create.cshtml
#model MyBlog.Models.Article
#{
Layout = "~/Views/Shared/_AdminLayout.cshtml";
ViewBag.Title = "Create article";
}
<h2>Create article</h2>
#using (Html.BeginForm("Create", "Article", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Title, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Title, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Title, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Content, htmlAttributes: new { #class = "control-label col-md-2" })
<div ass="col-md-10">
#Html.TextAreaFor(model => model.Content,new { #id = "Content", #class = "form-control", #rows = "200" })
#Html.ValidationMessageFor(model => model.Content, "", new { #class = "text-danger" })
<script>
CKEDITOR.replace("Content");
</script>
</div>
</div>
<div class="col-md-offset-2 col-md-10">
<input id="Submit" type="submit" value="submit" />
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Article controller:
// POST: Article
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind("Title,Content")] Article article)
{
try
{
return RedirectToAction("Index");
}
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
return View();
}
// GET: Article/Create
[HttpGet]
public ActionResult Create()
{
return View();
}
When I submit form. I see a url like this appears:
xxx//localhost:7158/article/create?Title=a&Content=b__RequestVerificationToken=CfDJ8JLgrvFS_U1JlinCQaKFM9rmomKaF5pDFJjX5Mbp7_OCoQq2hNZ6ygB05XZd-Qy8osia_h_1i1nzXuk5lZWQRBSTsId3hu-lbcapc3xDViukVhv6xeMv_ekiCyW6HdFkFh8iBzjXhJ9bRnZyrnP651U
Debug on VS studio
I have found this bug. If I change Layout shared to null. It working. So I have modified my shared layout. Tks for your help !

ASP.NET MVC - Compound Model

I have a trivial web app with the following model:
public class SillyModel
{
public SillyModel()
{ Id = Guid.NewGuid(); Children = new List<SillyModel>(); }
[Key]
public virtual Guid Id { get; set; }
public virtual string Value { get; set; }
public virtual List<SillyModel> Children { get; set; }
}
}
I have an Edit View of:
#model WebApplication1.Models.SillyModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.Partial("EditPartial", Model)
<div class="form-horizontal">
<h4>SillyModel</h4>
<hr />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
With the Partial:
#model WebApplication1.Models.SillyModel
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.Value, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Value, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Value, "", new { #class = "text-danger" })
</div>
#foreach (var item in Model.Children)
{
#Html.Partial("EditPartial", item)
}
</div>
The rendering is Fine! But for the life of me (well at least 3 days of struggle) I can not get it so that the returned model is properly bound! [No children are returned]
I am at my wits end.
You have to re-structure the way you are coding a little
In the Views/YourController, add a folder called EditorTemplates if not yet exists and add a view named SillyModel and copy the code from EditPartial to this new view
You change the foreach to for loop to decorate the controls with index
The code
~/Views/YourController/EditorTemplates/SillyModel.cshtml
#model WebApplication1.Models.SillyModel
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.Value, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Value, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Value, "", new { #class = "text-danger" })
</div>
#for (var index=0; index<Model.Children.Count;index++)
{
#Html.EditorFor(model=>Model.Children[index])
}
</div>
~/Views/YourController/Edit
instead of #Html.Partial("EditPartial", Model), use #Html.EditorFor(m=>m)
Explanation
By adding the EditorTemplates/SillyModel , now you can call #Html.EditorFor(model=>Model.Children[index]) and your custom editor will be rendered
You need to use indexed controls in order Binding to the Model succeeded
Hope this will help you

Unable to Call a method if i mentioned its attribute as HttpPost

I have a signin page which users can use to signin,i defined its method in controller as HttpPost,when i try to access it from browser,it shows no file found,if i remove the HttpPost attribute it hits the controller and return the view.Eventhough it pass the values in the form to controller if i didnt mentioned its type as HttpPost .Here my code of Login/SignIn.cshtml:
#model BOL.Player_Access
#using (Html.BeginForm("SignIn","Login",FormMethod.Post)){
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Sign In</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.PlayerEmail, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PlayerEmail, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PlayerEmail, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Password, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Password, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Password, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="SignIn" class="btn btn-default" />
</div>
</div>
</div>
And my LoginController Code is here
[AllowAnonymous] //This filter removes authorize filter for this controller alone and allow anonyomous request
public class LoginController : Controller
{
// GET: Login
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult SignIn(Player_Access plyr_obj)
{
return View();
}
}
You need code for both GET and POST.
You need a GET action to show the form that will be POST'ed
public ActionResult SignIn()
{
return View();
}
This will show the SignIn view.
Then you need a POST action to take the values from the form and send them to the controller.
[HttpPost]
public ActionResult SignIn(Player_Access plyr_obj)
{
//do some work to authenticate the user
return View();
}

How could I store an "id" (urlparameter.optional) through a textboxfor()

I'm trying to store an id from url in a textboxfor(), but I don't obtain the result. When i put "id" in a textbox, I obtain the correct result. Instead, in a textboxfor(model=> model.IDCantiere,new{#value="id"}). It doesn't run; please help me, I've attached the code fragments:
Controller.cs
// GET: CantiereOres/Create
public ActionResult Create(int id)
{
ViewBag.id = id;
//ViewBag.IdCantiere = new SelectList(db.Cantieres, "IdCantiere", "IdCantiere");
ViewBag.IdPersona = new SelectList(db.CantierePersones, "IdPersona", "Persona");
return View();
}
// POST: CantiereOres/Create
// Per proteggere da attacchi di overposting, abilitare le proprietà a cui eseguire il binding.
// Per ulteriori dettagli, vedere https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "IdOre,IdCantiere,IdPersona,Ore,datetime")] CantiereOre cantiereOre)
{
if (ModelState.IsValid)
{
db.CantiereOres.Add(cantiereOre);
db.SaveChanges();
return RedirectToAction("details", "Cantieres", new { id = cantiereOre.IdCantiere });
}
ViewBag.IdCantiere = new SelectList(db.Cantieres, "IdCantiere", "IdCantiere", cantiereOre.IdCantiere);
ViewBag.IdPersona = new SelectList(db.CantierePersones, "IdPersona", "Persona", cantiereOre.IdPersona);
return View(cantiereOre);
}
View.cshtml:
#model archeagroup.Models.CantiereOre
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.TextBox("id")
<div class="form-horizontal">
<h4>CantiereOre</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.IdCantiere, "IdCantiere", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model=>model.IdCantiere, new {#value ="id", #class = "form-control" } )
#*#Html.EditorFor(model => model.IdCantiere, new { htmlAttributes = new { #class = "form-control" } })*#
#Html.ValidationMessageFor(model => model.IdCantiere, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.IdPersona, "IdPersona", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("IdPersona", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.IdPersona, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Ore, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Ore, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Ore, "", 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", "Details", "Cantieres")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
If you want 2441 to be in this textbox right?
#Html.TextBoxFor(model=>model.IdCantiere, new {#value ="id", #class = "form-control" } )
Then in your controller, instead of this:
ViewBag.id = id;
Do this:
Model.IdCantiere = id;
And change your textbox to:
#Html.TextBoxFor(model=>model.IdCantiere, new { #class = "form-control" })
Which will put (from your screenshot) 2441 in the textbox you indicated.
EDIT: So you need to return an instance of archeagroup.Models.CantiereOre from your controller Create method:
public ActionResult Create(int id)
{
var model = new acheagroup.Models.CantiereOre();
model.IdCantiere = id;
model... //other assignments - use this instead of ViewBag
return View(model);
}
With this the above code will work since it will be fed from the model.

ViewBag does not work

I'm trying to create an MVC site and I have some trubble with the ViewBag.
It's like it can't contain elements.
I'm using MVC 4.
This is my function in the controller:
public ActionResult Create()
{
ViewBag.DiscID = new SelectList(entities.Disc, "ID", "ID");
ViewBag.StarID = new SelectList(entities.Star, "ID", "Name");
ViewBag.Num = 7;
return View();
}
This is my Create view:
#model Movies.Models.StarAndRole
#{
ViewBag.Title = "Create";
int num;
int.TryParse(ViewBag.Num, out num);
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>StarAndRole</h4>
<h3>num is = #num</h3>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.StarID, "Star", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("StarID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.StarID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.DiscID, "Disc Num", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("DiscID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.DiscID, "", 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")
}
It appears to me that everything I put in the ViewBag is NULL.
What am i doing wrong?
Thank you in advance!
Int.TryParse method accepts a string (representation of the int value). But you are passing a dynamic type to the method. It should give you an error about that.
If you know you are setting an Int value from your action method, in your view, you can simply read it like
int num = ViewBag.Num;
A better solution is to add the data you want to pass to the view to your view model.
public class YourCreateVm
{
public string Description { set;get;}
public int Num { set;get;}
public List<SelectListItem> Disks { set;get;}
public int SelectedDiskId { set;get;}
}
and in your GET view
public ActionResult Create()
{
var vm = new YourCreateVm { Num = 7 };
vm.Disks = entities.Disc.Select(s=> new SelectListItem
{ Value=s.ID.ToString(), Text =s.Name}).ToList();
return View(vm);
}
and in your view
#model YourCreateVm
#using(Html.BeginForm())
{
<p>My num is : #Model.Num <p>
#Html.TextBoxFor(s=>s.Description)
#Html.DropDownListFor(s=>s.SelectedDiskId, Model.Disks)
<input type="submit" />
}

Resources