Model binder data issue from a EditorTemplate - asp.net

I'm showing in a List in a Razor view. In it I have several Editor templates that are displayed in the list view. Here is my editor template.
#using Contoso.MvcApplication.Extensions
#model Contoso.MvcApplication.ViewModels.MultipleChoiceQuestionViewModel
<h5>#Model.Question.QuestionText</h5>
<div>
#Html.RadioButtonForSelectList(m => m.Question.SelectedAnswer, Model.AnswerRadioList)
#Html.ValidationMessageFor(m => m.Question.SelectedAnswer)
</div>
The issue is where I set the RadioButtonForSelectList, it's binding so so, because I know at this situation should be inside a for loop like this:
#Html.RadioButtonForSelectList(m => m[i].Question.SelectedAnswer, Model.AnswerRadioList) // the index
But from the Editor template, I have no way to know the index inside a lambda expression.
This is the site where I copied the html extension from:
http://jonlanceley.blogspot.mx/2011/06/mvc3-radiobuttonlist-helper.html
And here is the view model that I'm using
public class MultipleChoiceQuestionViewModel
{
public MultipleChoiceQuestion Question { get; set; }
public List<SelectListItem> AnswerRadioList { get; set; }
}
How do I correctly bind the radioButton?
When I read the tag in code, all the models in my list have the same id: Question.SelectedAnswer. I assume this is wrong, because there should be an indexed ID like so: Question.SelectedAnswer.[INDEX].
UPDATE:
public ActionResult Index(short testId)
{
GenerateQuiz(testId);
StartQuiz();
return View(CreateQuestionViewModel((MultipleChoiceQuestion)CurrentQuestion));
}
[HttpPost]
public ActionResult Index(MultipleChoiceQuestionViewModel q)
{
// Save answer state
((MultipleChoiceQuestion)CurrentQuestion).SelectedAnswer = q.Question.SelectedAnswer;
if (CurrentNumber == Questions.Count - 1)
{
QuizCompleted();
return RedirectToAction("ShowResults");
}
else
{
NextQuestion();
return View(CreateQuestionViewModel((MultipleChoiceQuestion)CurrentQuestion));
}
}

The part of the view that displays the questions should look like this:
#for (int j = 0; j < Model.Questions.Count; j++)
{
<h5>
Model.Questions[j].QuestionText
</h5>
<div>
#Html.RadioButtonForSelectList(m => m.Questions[j].SelectedAnswer, Model.AnswerRadioList)
</div>
}

Related

ASP.NET MVC Conditional ViewModel Abstraction

I am new to ASP.NET MVC and I am stuck on a point. I am working on a classified site. My situation is, I have a lot of categories in which a user can post their ads and each ad category have different View. I have created a Controller Action like
public ActionResult PostAd(string CategoryName, string SubCategoryName)
{
if(categoryName == "Vehicle" && SubCategoryName == "Cars")
{
var model = new CarAdViewModel();
// set CarAdViewModel properties...
return View("CarAdCreateView", model);
}
else if(categoryName == "Vehicle" && SubCategoryName == "Bikes")
{
var model = new BikeAdViewModel();
// set BikeAdViewModel properties...
return View("BikeAdViewModel", model);
}
else if(categoryName == "Property" && SubCategoryName == "RentHouse")
{
var model = new RentHouseAdViewModel();
// set RentHouseAdViewModel properties...
return View("RentHouseAdViewModel", model);
}
else................... so on and so on
}
My problem is I have huge number of Categories and Sub Categories almost 60+. And if I keep on coding like above for 60+ categories and subcategories, my PostAd method is going to blast and become unmanageable.
Please tell me some best practice or pattern which can bring me out of this problem.
Unfortunately, some of what you are doing cannot be avoided. There needs to be some form of model and view selection based on category.
Use a factory pattern. Create a base class:
public abstract class BaseCategory
{
public abstract string GetViewName();
public abstract Object CreateModelFromFormData();
}
For each category, create a sub-class derived from BaseCategory and implement the abstract functions.
In your action, do the following:
public ActionResult PostAd(string categoryName, string subCategoryName)
{
BaseFactory factory;
if (categoryName == "Vehicle")
{
if (subCategoryName == "Cars")
{
factory = new CarsFactory();
}
else ...
}
else ...
return View(factory.GetViewName(), factory.CreateModelFromFormData());
}
I have a couple reasons for this schema:
I am purposefully using if/else for the factory selection. Your controller is going to be created and re-created for every action call. So pre-populating a list will constantly and needlessly create objects for categories that will not be selected. A simple if/else will be more efficient. If you want to prevent the if/else, you can put your factories in a Dictionary and select based on the categories, but that would be a lot of needless constructor actions.
I made the CreateModelFromFormData a function because I assume you'll need to copy data from the posted form data. This may require passing in data, but I left the function parameterless.
I used base/derived classes because the copying of the form data will probably need to be custom from the model being created and the form data being posted. Also, saving to persistent storage (file or database) may be category-specific as well.
It would be one of some possible solutions
public class PostAdData
{
public string CategoryName;
public string SubCategoryName;
public string ViewName;
public Type Model;
}
public class PostController : Controller
{
private readonly List<PostAdData> _theData;
public HomeController()
{
_theData = InitializeData();
}
public ActionResult PostAd(string categoryName, string subCategoryName)
{
var data = _theData.FirstOrDefault(c => c.CategoryName == categoryName && c.SubCategoryName == subCategoryName);
if (data != null)
{
var model = Activator.CreateInstance(data.Model);
return View(data.ViewName, model);
}
return View("Error");
}
[NonAction]
public List<PostAdData> InitializeData()
{
var result = new List<PostAdData>
{
new PostAdData
{
CategoryName = "Vehicle",
SubCategoryName = "Cars",
ViewName = "CarAdCreateView",
Model = typeof (CarAdViewModel)
}
};
return result;
}
}
You should make this data driven. You create a lookup table that has a compound primary key of category and subcategory. Then it has a table with View in it. Then you simply ad rows for each category/subcategory/view combination.
If you absolutely don't want a database, then you can use a simple hashset or dictionary.
var views = new Dictionary<Tuple<string,string>,string>();
views.Add(new Tuple<string,string>("Vehicle", "Cars"), "CarAdCreateView");
Then in your PostAd you just lookup the correct view.
What a beautiful solution on www.asp.net to my question, here is the link : http://forums.asp.net/t/1923868.aspx/1?ASP+NET+MVC+Conditional+ViewModel+Abstraction
Edit:
My code is :
public class AdsController : Controller
{
private readonly IAdService _adService;
public AdsController(IAdService adService)
{
_adService = adService;
}
public ActionResult PostAd(string Category, string SubCategory)
{
//Here I will call
var strategy = GetStrategy(CategoryName, SubCategoryName);
strategy.FillModel(_adService );
return View(strategy.ViewName, strategy.Model);
}
}

How to implement "Edit" method for complex model in asp.net MVC 3

In my mvc 3 web store app I have this model: Items, Products, Shipping, Users tables, where 1 Item contains 1 Product, 1 User, and 1 Shipping. I need to somehow create and edit items. With creating there are not many problems cause I can pass the "fields to fill" as parameters to Crete(Post) method. With editing there is one problem - Null Reference Exception occures. This is my code:
(controller):
I pass model (of type Item) to Edit Post method as a parameter and get product and shipping, that, I hope), I filled with values.
[HttpGet]
public ViewResult Edit(int itemId)
{
Item target = _itemsRepository.GetItem(itemId);
return View(target);
}
[HttpPost]
public ActionResult Edit(Item model)
{
Product p = model.Product;
Shipping s = model.Shipping;
// here goes some validation stuff
if (ModelState.IsValid)
{
_productRepository.UpdateProduct(p);
_shippingRepository.UpdateShipping(s);
return RedirectToAction("Content");
}
return View();
}
Strongly typed view (where I fill the form):
#model WebStore.WebStoreModels.Item
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Product</legend>
#Html.HiddenFor(i => i.ItemId)
<p>Name: </p>
<p>
#Html.EditorFor(i => i.Product.Name)
#Html.ValidationMessageFor(i => i.Product.Name)
</p>
// and so on
</fieldset>
<fieldset>
<legend>Shipping</legend>
<p>Cost: </p>
<p>
#Html.EditorFor(i => i.Shipping.Cost)
#Html.ValidationMessageFor(i => i.Shipping.Cost)
</p>
// so on
</fieldset>
<p>
<input type="submit" value="Save"/>
</p>
}
And when I fill the form and click "safe" button the NullReferenceException occures in the ProductRepository class in UpdateProduct() method:
public class ProductRepository : IProductRepository
{
private WebStoreDataContext _dataContext;
public ProductRepository(WebStoreDataContext dataContext)
{
_dataContext = dataContext;
}
public Product GetProduct(int productId)
{
return _dataContext.Products.SingleOrDefault(p => p.ProductId == productId);
}
public void UpdateProduct(Product p)
{
var dbProduct = GetProduct(p.ProductId);
dbProduct.Name = p.Name; // here the exception occures
dbProduct.Description = p.Description;
dbProduct.Price = p.Price;
dbProduct.Category = p.Category;
dbProduct.SubCategory = p.SubCategory;
_dataContext.SubmitChanges();
}
Seems like I can't use this assignment:
Product p = model.Product;
I also tried: (in view and then assign it to Product in Edit(Post) method)
TempData["product"] = #Model.Product;
And also: (in view)
#Html.Hidden("product", #Model.Product)
And pass it as parameters to Edit(Post) method:
[HttpGet]
public ViewResult Edit(Product p, Shipping s)
I think the problem is associated with model.
Any help would be great, Sorry for so much code)
You need to add hidden ProductId input inside of the form in your View:
#using (Html.BeginForm())
{
#Html.HiddenFor(i => i.Product.ProductId)
...
}
The reason you need to have this line is that when you submit your form model binder looks at every input (including hidden inputs) and binds them to the properties of your Item model that you pass on [HttpPost] action. Before, when you didn't have this line, only the following properties were populated:
Item.ItemId
Item.Product.Name
Item.Shipping.Cost
Your model didn't have information for the value of Item.Product.ProductId. This property is int, which means that it was turning to be equal 0 when form was submitted. Inside your _productRepository.UpdateProduct(p); method you are trying to get a product by Id and obviously it cannot find a product with id = 0, which is why this call was returning null resulting in a null reference exception on the next line.

Import two or multiple class models to a single controller on ASP.NET

I'm very new to ASP.NET, but I know a little programming in Java. I want to use a ZIP code to query a database which will return a string, then use that string to query another database. I wanted to do this on the same control model. I thought it would be easy, and it sounds pretty easy.
When I created the controller, I put the model class of the first database, and, so far, I've gotten as far as querying the first database, but now that I have the string I want to query a second database through the DBEntities.
This displays an error saying:
> The model item passed into the dictionary is of type
> 'System.Collections.Generic.List`1[FinalBallot.Models.AgainCandidate]',
> but this dictionary requires a model item of type
> 'System.Collections.Generic.IEnumerable`1[FinalBallot.Models.ZipTable]'.
Is there a way to solve this in an easy way?
public class Default1Controller : Controller
{
private CandidatesDBEntities db = new CandidatesDBEntities();
public string districString = "";
//
// GET: /Default1/
public ViewResult Index(string searchString)
{
var queryZip = from s in db.ZipTables select s;
var queryCandidates = from s1 in db.AgainCandidates select s1;
double sT = 0;
//method so it doesnt display the whole db
if (String.IsNullOrEmpty(searchString))
{
queryZip = queryZip.Where(s => s.ZipL.Equals(0));
}
if (!String.IsNullOrEmpty(searchString))
{
sT = double.Parse(searchString);
queryZip = queryZip.Where(s => s.ZipL.Equals(sT));
try
{
districString = queryZip.ToList().ElementAt(0).District;
}
catch
{
}
if (!String.IsNullOrEmpty(districString))
{
queryCandidates = queryCandidates.Where(s1 => s1.District.Equals(districString));
}
}
return View(queryCandidates.ToList());
}
In your view, did you specify the model to be IEnumerable<ZipTable>? The model that you're passing to your view is IEnumerable<AgainCandidate>, so you would get an error if you specified your model as something else. You'd need to change the model in your view to be IEnumerable<AgainCandidate>.
UPDATE:
Based on your revised explanation, you can do a couple things:
1) create a "ViewModel" that has two properties for each of your collections you want to display on the page like so:
public class MyViewModel
{
IEnumerable<ZipTable> Zips { get; set; }
IEnumerable<AgainCandidate> Candidates { get; set; }
}
Instantiate that in your action method and return that as your model. This would be my preferred approach.
2) Stash your two collections in the ViewData bag in your action method:
ViewData["Zips"] = queryZip.ToList();
ViewData["Candidates"] = queryCandidates.ToList();
return View(ViewData);
You can pull this data in your view like this:
#foreach (var zip in ViewData["Zips"] as IEnumerable<ZipTable>)
{
...
}

Deciding content of html tag based on a conditional in mvc3 asp.net

This sounds really basic yet I couldn't find the answer.
I pass a Message struct to the View and I want to display it.
If the Message.Category field is "Technical" I want to display "Technical Problem" else just display it as it is.
How do I make the view understand that Technical Problem isn't a statement but html text I want to display?
My code:
<span class="cright" id="cat">
#{
if (String.Compare(ViewBag.Message.Category, "Technical") == 0)
{
Technical Problem <----THIS
}
else #ViewBag.Message.Category
}
</span>
More info:
I'm working on a messaging system. Users create a message and as it is being sent they can view it. The category is compulsory (Question, Suggestion or Technical Problem) and to avoid redundancy in the database I truncate the last option to just 'Technical', however when the users view their sent message I want it to show up in full.
Thanks everyone; from all your answers I arrived at:
<span class="cright" id="cat">
#if (ViewBag.Message.Category == "Technical ")
{<text>Technical Problem</text>}
else
{<text>#ViewBag.Message.Category</text>}
</span>
which works just as I wanted.
Original Answer
if(ViewBag.Message.Category == "Technical")
{
<span>Technical problem</span>
}
else
{
<span>Problem is : #(ViewBag.Message.Category)</span>
}
Updated Answer
//Model
public class Message
{
public int ID {get; set;}
public string Message {get; set;}
public string Category {get; set;}
}
//Controller
public ActionResult Index()
{
//If you use Linq to Sql, I made this up but this should give you an idea
using(MessageDataContext context = new MessageDataContext())
{
var messages = context.Messages.Where(m => m.Category == "Technical")
.Select(m => new Message { ID = m.ID, Message = m.Text, Category = m.Category});
return View(messages);
}
}
//View
#model IEnumerable<Message>
#foreach(var message in Model)
{
if(message.Category == "Technical")
{
//Id and class will be "TechnicalMessage1"
//Now you can create css class called "TechnicalMessage1" and only message with Id = 1 will have it
<span id="#(message.Category)Message#(message.ID)" class="#(message.Category)Message#(message.ID)">Technical problem</span>
}
else
{
<span>Problem is : #(ViewBag.Message.Category)</span>
}
}
You can use
<text>Technical Problem</text>
for this.
Is this what you are looking for?
#("Technical Problem")
alternatively
<text>Technical Problem</text>
alternatively
#:Technical Problem
Read http://haacked.com/archive/2011/01/06/razor-syntax-quick-reference.aspx for the syntax ..

Asp.Net Gridview - One Column is List<string> - Want to Show Only The Last Item

I have an Asp.Net GridView. One of the Columns is a List, but I only want to show the Last Item in the list. How can I do this?
List<string> Column1
I am binding the Gridview to a business object:
public Class GridObject
{
List<string> Column1 { get; set; }
}
EDIT
This worked, but is it the best solution:
<%# ((List<string>)Eval("Column1"))[((List<string>)Eval("Column1")).Count - 1] %>
I would add a property to the object you are binding to, and use that property instead of the list property in your binding.
public Class GridObject
{
List<string> Column1 { get; set; }
public string Column1LastValue
{
get
{ // return Column1.Last(); if linq is available
return Column1[Column1.Count-1];
}
}
}
Edit: Adding a presentation wrapper allows you to unit test what will be displayed. You are doing a translation in the view, which is OK, but since you technically have some logic happening to translate your business object to something proper for display, you would likely want to unit test that translation. Then, any formatting you want to apply to any of your business object fields is wrapped in a testable class, rather than hidden on the untestable view. Here is a sample of how this could be done:
public class GridObjectView
{
private GridObject _gridObject;
public GridObjectView(GridObject gridObject)
{
_gridObject = gridObject;
}
public string Column1
{
get
{
return _gridObject.Column1.Last();
}
}
}
Then to do the databinding, you could do this:
List<GridObject> data = GetGridData();
grid.DataSource = data.Select(g => new GridObjectView(g));
grid.DataBind();
Your best bet is to create a template column and use an inline script to retrieve the value from the list:
<%= ((List<string>)DataBinder.Eval("Column1"))[((List<string>)DataBinder.Eval("Column1")).Count] %>
Or you could store the result in the text of a label or a literal.
Hope that helps

Resources