Behavior while passing objects inside objects in asp.net mvc? - asp.net-mvc-2

I am sure everyone has come across this but I thought will ask this anyways. So here is what I have -
public class ABC
{
public int x;
public int y;
public XYZ obj;
}
public class XYZ
{
int x1;
int y1;
}
public ActionResult Test1()
{
ABC model= new ABC();
model.x=1;
model.y=2;
ABC.obj= new XYZ();
model.x1=12;
obj.y2=34;
return View(model);
}
[HttpPost]
public ActionResult Test1(ABC model)
{
//does not get XYZ obj
}
View-
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Models.ABC>" %>
<% using (Html.BeginForm())
{%>
//stuff here
<%:Html.HiddenFor(model => model.obj)%>
<%}%>
If I do the hidden fields for explicitly for XYZ's fields x1 and y1 then I get back those values. Like this -
<%:Html.Hidden("Model.obj.x1",Model.obj.x1)%>
I guess this is expected behavior but am I missing anything here ?

Well, for one thing, your "inherits" attribute is wrong. Instead of
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="Models.ABC" %>
It should be
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<Models.ABC>" %>
If you want to use Models.ABC as your model. For another, the action methods you posted aren't even compilable, so it's difficult to tell what the real problem might be.
Sending composite objects like this works just fine for me, so there is most likely an issue with your implementation.
Update
Values for any persisted model properties have to be POSTed back from the editor page, which means they need to be stored in form fields. If the page generator isn't creating fields for those values (and I'm not sure it should - it would make more sense to me to include a partial view for nested objects), you'll need to add fields that are either editable or hidden.

Related

MVC 2: render HTML TextBoxFor based on a model containing a collection attribute

I am new to MVC and am still struggling a bit with what I think is pretty basic mvc stuff. My intended purpose is simply to render textboxes that represent a collection of data objects such that I can validate them easily using DataAnnoations. I've read and understand this tutorial, and have gotten it to work just fine - but it's only validating a single person object one at a time, composed of primitives:
http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx
My problem is similar to this post, but I was unsuccessful in applying it to my situation with no Razor:
MVC 3 client-side validation on collection with data annotations -- not working
Validation works great on "primitives" like String and int, but I don't understand validation on collections.
Here is the class that represents one instance of my object and some validation:
public class vc_EBTOCRecord
{
[StringLength(10, ErrorMessage = "must be under 10 chars")]
[Required(ErrorMessage = "Problem!")]
public String ItemText;
}
Here is the model that the view inherits:
public class EBTOCViewModel
{
public List<vc_EBTOCRecord> VcEbtocRecordList { get; set; }
}
Here is my controller:
public ActionResult Create()
{
EBTOCViewModel ebtocViewModel = new EBTOCViewModel();
List<vc_EBTOCRecord> vcEbtocRecordList = new List<vc_EBTOCRecord>();
vc_EBTOCRecord vcEbtocRecord = new vc_EBTOCRecord();
vcEbtocRecord.ItemText = "bob";
vcEbtocRecordList.Add(vcEbtocRecord);
vc_EBTOCRecord vcEbtocRecord2 = new vc_EBTOCRecord();
vcEbtocRecord.ItemText = "fred";
vcEbtocRecordList.Add(vcEbtocRecord2);
vc_EBTOCRecord vcEbtocRecord3 = new vc_EBTOCRecord();
vcEbtocRecord.ItemText = "joe";
vcEbtocRecordList.Add(vcEbtocRecord3);
ebtocViewModel.VcEbtocRecordList = vcEbtocRecordList;
return View(ebtocViewModel);
}
and lastly, here is what I am attempting in my view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<vcAdminTool.ViewModels.EBTOCViewModel>" %>
<h2>Create</h2>
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<% foreach(var thing in Model.VcEbtocRecordList)
{
Html.TextBoxFor(model => thing.ItemText);
//Html.TextBox("hello", thing.ItemText ); didn't work...
//Html.TextBox("hello"); nope, still didn't work
Response.Write("a record is here");
}
%>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<div>
<%: Html.ActionLink("Back to List", "Index") %>
</div>
When the view renders, "a record is here" is printed three times, indicating to me that MVC recognizes the underlying object I have created, but why won't it render three textboxes?
The desired result is to have three blank validated textboxes. I will then write code that writes the information back to the database if the form is valid.
If you please, what obvious thing am i missing?

Creating a generic edit view with ASP.NET, MVC, and Entity Framework

In my database, I have 40 tables that contain only an ID number and a name. My database is accessed using Entity Framework. While I have no trouble editing them each by generating a strongly-typed view and postback methods for each object, I would like to create a more generic method and view for viewing and editing these objects.
I am currently using the following code to access each object. In this case, it is for an object of 'AddressType':
public ActionMethod EditAddressType(int ID)
{
var result = database.AddressType.Single(a => a.ID == ID);
View(result);
}
[HttpPost]
public ActionMethod EditAddressType(int ID, FormCollection formValues)
{
var result = database.AddressType.Single(a => a.ID == ID);
UpdateModel(result);
database.SaveChanges();
return View("SaveSuccess");
}
The view 'EditAddressType' is strongly typed and works fine, but there's a lot of repeated code (one instance of this for each object). I've been told that I need to use reflection, but I'm at a loss for how to implement this. My understanding is that I need to retrieve the object type so I can replace the hardcoded reference to the object, but I'm not sure how to get this information from the postback.
I've had success binding the information to ViewData in the controller and passing that to a ViewPage view that knows to look for this ViewData, but I don't know how to postback the changes to a controller.
Thanks for any help you can give me!
If you are going to edit the object you don't need to refetch it from the database in your POST action. The first thing would of course be to abstract my data access code from the controller:
public class AddressesController: Controller
{
private readonly IAddressesRepository _repository;
public AddressesController(IAddressesRepository repository)
{
_repository = repository;
}
public ActionMethod Edit(int id)
{
var result = _repository.GetAddress(id);
return View(result);
}
[HttpPut]
public ActionMethod Update(AddressViewModel address)
{
_repository.Save(address);
return View("SaveSuccess");
}
}
You will notice that I have renamed some of the actions and accept verbs to make this controller a bit more RESTFul.
The associated view might look like this:
<% using (Html.BeginForm<AddressesController>(c => c.Update(null))) { %>
<%: Html.HttpMethodOverride(HttpVerbs.Put) %>
<%: Html.HiddenFor(model => model.Id) %>
<%: Html.TextBoxFor(model => model.Name) %>
<input type="submit" value="Save" />
<% } %>
As far as the implementation of this IAddressesRepository interface is concerned, that's totally up to you: Entity Framework, NHibernate, XML File, Remote Web Service call, ..., that's an implementation detail that has nothing to do with ASP.NET MVC.

Controller as ascx factory - bad idea?

I'm trying to create something like *.ascxs' factory.
Scenario:
I would like to render controls which depends on model, which i've passed to partialView.
I'd like to achieve something like this:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MyAbstractModel>" %>
<%= Model.Property1 %>
<!-- other more sophisticated displays on model -->
<% Html.RenderAction("RenderControl", "Factory", new { model = Model}); %>
FactoryController:
public ActionResult RenderControl(object model) {
if (model.GetType() == typeof(Model1) {
return RenderPartial("Partial2", model);
} else {
return RenderPartial("Partial1", model);
}
}
I'd like to know is there any better way to cope with such situation. I suppose It's not the most efficient method to build web page in ASP.MVC 2.
If this method is acceptable, how can i restrict access to such controller? I would like to use this class only on server side and only by ascxs' pages
Use the ChildActionOnly() attribute to restrict access to your actions.
What you are trying to do is already builtin to MVC: Html.DisplayFor()
See: http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction.html

Where and how to load dropdownlists used in masterpage

I'm new to MVC!
I am trying to use two DropDownLists (Cities, Categories) in a PartialView that will be used in MasterPage, meaning they will be visble all the time.
I tried to load them in HomeCOntroller, but that didn't work. I got an Exception.
I read something about making a baseController that the other controllers will inherit from, I have tried that, kind of, but I guess i'm doing something wrong.
This is the only code I got today:
Masterpage
<% Html.RenderPartial("SearchForm"); %>
PartialView (SearchForm.ascx)
<% using (Html.BeginForm("Search", "Search")) { %>
<% } %> // dont know why I need two BeginForms, if I dont have this the other form won't trigger at all! Weird!
<% using (Html.BeginForm("Search", "Search", FormMethod.Get)) { %>
<%= Html.DropDownList("SearchForm.Category", new SelectList(ViewData["Categories"] as IEnumerable, "ID", "Name", "--All categories--")) %>
<%= Html.DropDownList("Search.City", Model.Cities, "--All cities--") %>
<input name="search" type="text" size="16" id="search" />
<input type="submit" id="test" title="Search" />
<% } %>
Two question:
Where and how to load the DropDownLists is the problem. I have tried to load it in the HomeController, but when go to another page then it says that the DDLs is empty and I get a Excecption.
Why do I have to use two forms for the ActionMethod to trigger ?
Hope anyone can help me out!
It sounds like you're only setting the property for a single action result. The Model.Cities data will have to be populated for every single view that needs to use it.
One solution would be to move the population of it to an ActionFilter
public class CityListAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext) {
var result = filterContext.Result as ViewResult;
result.ViewData.Model = //populate model
base.OnActionExecuted(filterContext);
}
}
and then add the filter to your controller
[CityList]
public class HomeController : Controller {
public ActionResult Index() {
return View();
}
}
As for the two forms issue, there should be no reason that i can think of that you need an empty form.
Take a look at the html that's being output and make sure it's ok. Also check the action is being generated correcly
Better way to do this, is to create something like MasterController and have action method on it like this:
[ChildActionOnly]
public ActionResult SearchForm()
{
//Get city data, category data etc., create SearchFormModel
return PartialView(model);
}
I recommend you create strongly typed view (SearchForms.ascx of type ViewUserControl<SearchFormModel>). Also it may be a good idea to have a model like this:
public class SearchViewModel
{
public IList<SelectListItem> Cities { get; set; }
public IList<SelectListItem> Categories { get; set; }
}
and use a helper like this: http://github.com/Necroskillz/NecroNetToolkit/blob/master/Source/NecroNet.Toolkit/Mvc/SelectHelper.cs to convert raw data to DDL friendly format beforehand.
In any case, you now use Html.RenderAction() instead of Html.RenderPartial() and specify you want "SearchForm" action from "MasterController".

ASP.MVC 2 RTM + ModelState Error at Id property

I have this classes:
public class GroupMetadata
{
[HiddenInput(DisplayValue = false)]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
[MetadataType(typeof(GrupoMetadata))]
public partial class Group
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
And this action:
[HttpPost]
public ActionResult Edit(Group group)
{
if (ModelState.IsValid)
{
// Logic to save
return RedirectToAction("Index");
}
return View(group);
}
That's my view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Group>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) {%>
<fieldset>
<%= Html.EditorForModel() %>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
<div>
<%=Html.ActionLink("Back", "Index") %>
</div>
</asp:Content>
But ModelState is always invalid! As I can see, for MVC validation 0 is invalid, but for me is valid.
How can I fix it since, I didn't put any kind of validation in Id property?
UPDATE:
I don't know how or why, but renaming Id, in my case to PK, solves this problem.
Do you know if this an issue in my logic/configuration or is an bug or expected behavior?
Just before if (ModelState.IsValid) remove Id index using this line ModelState.Remove("Id") this way when MVC team remove this bug, you just need to remove this line code of your projects.
You have a required field, Id. It's required because it's non-nullable. You must either (1) submit it with your form or (2) change your model to make Id nullable or (3) use a different type.
I can confirm that removing the id index using ModelState.Remove("Id") works.
Could anyone give an elaborate explanation for this alleged "bug"?
Maybe someone from the Microsoft MVC Team could give an explanation?
There are no bugs like this when using the ADO.NET Entity Data Model as a data source - only on Linq To SQL.
Your problem is that you are using a property named 'id' in your model. Try using a different name and you will see it works. Mvc seems to have a problem with that.
I experienced the exact same issue.
One other reason can be:
If you declare your key as some type other than a number e.g String, then since by default it can't auto generate the keys for you, you'll face this error.