Creating a generic edit view with ASP.NET, MVC, and Entity Framework - asp.net-mvc-2

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.

Related

Best way to access ViewData value from jQuery method in ASP.NET MVC2

I want to access a public property called UserType from within jQuery method which is filled with the data from database call in a controller method.
The same property value needs to be set from multiple controller methods. I filled the data in ViewData and tried to access it in jquery method as mentioned below:
TestController.cs:
[Authorize(Roles = "Root")]
public ActionResult Index()
{
var user = _dbService.GetUser(_profile.UserName);
ViewData["UserType"] = user.UserType;
}
index.aspx:
<% var serializer = new System.Web.Script.Serialization.JavaScriptSerializer(); %>
<script type="text/javascript">
$(function () {
var userType = <%= serializer.Serialize(ViewData["UserType"]) %>;
alert(userType);
});
</script>
I am able to access the UserType value successfully, but I need to use the below mentioned code in all the controller action methods.
ViewData["UserType"] = user.UserType;
which is not a good design practice.
Can anyone help me know any other best alternative to manage the above mentioned change with some sample code?

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

How to support multiple forms that target the same Create action on the same page?

I want to have two separate forms on a single create page and one action in the controller for each form.
In the view:
<% using (Html.BeginForm()) { %>
// Contents of the first (EditorFor(Model.Product) form.
<input type="submit" />
<% } %>
<% using (Html.BeginForm()) { %>
// Contents of the second (generic input) form.
<input type="submit" />
<% } %>
In the controller:
// Empty for GET request
public ActionResult Create() {
return View(new ProductViewModel("", new Product()));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Product product) {
return View(new ProductViewModel("", product));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(string genericInput) {
if (/* problems with the generic input */) {
ModelState.AddModelError("genericInput", "you donkey");
}
if (ModelState.IsValid) {
// Create a product from the generic input and add to database
return RedirectToAction("Details", "Products", new { id = product.ID });
}
return View(new ProductViewModel(genericInput, new Product()));
}
Results in "The current request for action 'MyMethod' on controller type 'MyController' is ambiguous between the following action methods" - error or the wrong Create action is called.
Solutions?
Combine those two POST Create actions
into one public ActionResult
Create(Product product, string
genericInput);
Name one of the POST Create actions differently and add the new name to the corresponding Html.BeginForm()
I have no idea what are the caveats in these. How would you solve this?
You cannot have two actions with the same name and verb that differ only with argument types. IMHO naming your two actions differently would be a good idea assuming that they perform different tasks and take different inputs.
Actually, I believe you can do this if you are more specific with your BeginForm() call.
Using(Html.BeginForm<ControllerName>(c => c.Create((Product)null)) { }
Using(Html.BeginForm<ControllerName>(c => c.Create((string)null)) { }

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.NET MVC2: Getting textbox data from a view to a controller

I'm having difficulty getting data from a textbox into a Controller. I've read about a few ways to accomplish this in Sanderson's book, Pro ASP.NET MVC Framework, but haven't had any success.
Also, I've ran across a few similiar questions online, but haven't had any success there either. Seems like I'm missing something rather fundamental.
Currently, I'm trying to use the action method parameters approach. Can someone point out where I'm going wrong or provide a simple example? Thanks in advance!
Using Visual Studio 2008, ASP.NET MVC2 and C#:
What I would like to do is take the data entered in the "Investigator" textbox and use it to filter investigators in the controller. I plan on doing this in the List method (which is already functional), however, I'm using the SearchResults method for debugging.
Here's the textbox code from my view, SearchDetails:
<h2>Search Details</h2>
<% using (Html.BeginForm()) { %>
<fieldset>
<%= Html.ValidationSummary() %>
<h4>Investigator</h4>
<p>
<%=Html.TextBox("Investigator")%>
<%= Html.ActionLink("Search", "SearchResults")%>
</p>
</fieldset>
<% } %>
Here is the code from my controller, InvestigatorsController:
private IInvestigatorsRepository investigatorsRepository;
public InvestigatorsController(IInvestigatorsRepository investigatorsRepository)
{
//IoC:
this.investigatorsRepository = investigatorsRepository;
}
public ActionResult List()
{
return View(investigatorsRepository.Investigators.ToList());
}
public ActionResult SearchDetails()
{
return View();
}
public ActionResult SearchResults(SearchCriteria search)
{
string test = search.Investigator;
return View();
}
I have an Investigator class:
[Table(Name = "INVESTIGATOR")]
public class Investigator
{
[Column(IsPrimaryKey = true, IsDbGenerated = false, AutoSync=AutoSync.OnInsert)]
public string INVESTID { get; set; }
[Column] public string INVEST_FNAME { get; set; }
[Column] public string INVEST_MNAME { get; set; }
[Column] public string INVEST_LNAME { get; set; }
}
and created a SearchCriteria class to see if I could get MVC to push the search criteria data to it and grab it in the controller:
public class SearchCriteria
{
public string Investigator { get; set; }
}
}
I'm not sure if project layout has anything to do with this either, but I'm using the 3 project approach suggested by Sanderson: DomainModel, Tests, and WebUI. The Investigator and SearcCriteria classes are in the DomainModel project and the other items mentioned here are in the WebUI project.
Thanks again for any hints, tips, or simple examples!
Mike
try strongly typing the page to use SearchCriteria to autopost the data like that ex:
public partial class Search: ViewPage<SearchDetails>
This should do it for you (unable to verify this is perfect - typed this from memory):
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult SearchDetails(FormCollection formValues)
{
var txtContents = formValues["Investigator"];
// do stuff with txtContents
return View();
}
1.) Have you looked into ViewModels for your View? In essence that is what your SearchCriteria class is. Make sure you strongly type your view with that model:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/MyMaster.Master" Inherits="System.Web.Mvc.ViewPage<SearchCritieria>"
Also make sure that you use the HtmlHelper.TextBoxFor method to map this Investigator property to the SearchCritiera model. On Post back your text box value should be there:
'<%=Html.TextBoxFor(model => model.Invesigator)%>'
Good luck!
Also here is a great reference on using ViewModels that I have looked at a lot recently:
http://geekswithblogs.net/michelotti/archive/2009/10/25/asp.net-mvc-view-model-patterns.aspx
Thanks for the tips everyone. For learning purposes, I need to go back and follow the strongly typed route. I'm curious if I would have run into this problem if I would have done that from the beginning.
Until then, the following worked:
Use a submit button
Use this code for the form:
<% using(Html.BeginForm(new { Action = "SearchResults"})) { %> <% } >
Thanks again for you help!
Mike