Page loading twice, thus my model is empty - asp.net-mvc-3

I have been asking a lot of questions about creating routes and creating new pages and so on and so forth from a data base, I have the following model, control and the view below:
Model
namespace LocApp.Models
{
public class ContentManagement
{
public int id { get; set; }
[Required]
public string title { get; set; }
public string content { get; set; }
}
}
Controller
public ViewResult Index(string title)
{
using (var db = new LocAppContext())
{
var content = (from c in db.Contents
where c.title == title
select c).ToList();
return View(content);
}
}
View (partial)
#model IEnumerable<LocApp.Models.ContentManagement>
#{
foreach (var item in Model)
{
<h2>#item.title</h2>
<p>#item.content</p>
}
}
*View (Full) - Note that this code calls the _Content partial*
#model IEnumerable<LocApp.Models.ContentManagement>
#{
ViewBag.Title = "Index";
}
#{
if(HttpContext.Current.User.Identity.IsAuthenticated)
{
<h2>Content Manager</h2>
Html.Partial("_ContentManager");
}
else
{
Html.Partial("_Content");
}
}
When you go to site.com/bla the model is processed and contains information, but then it "magically" reloads, I break pointed through the controller and the view to watch this happen. On the second time the model is empty thus no content is displayed on the page.
My routes look s follows:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("favicon.ico");
routes.MapRoute(
"ContentManagement",
"{title}",
new { controller = "ContentManagement", action = "Index", title = UrlParameter.Optional }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
Update
It appears the issue is simple: the index is taking in a title, looping through and getting the contents, it then passes that to view like it should. but before the page is finished loading it loops through again, this time passing null as the title and thus loading an empty page.

The biggest issue that I see is that you are not actually passing your model to your partial views
#model IEnumerable<LocApp.Models.ContentManagement>
#{
ViewBag.Title = "Index";
}
#{
if(HttpContext.Current.User.Identity.IsAuthenticated)
{
<h2>Content Manager</h2>
Html.Partial("_ContentManager", Model);
}
else
{
Html.Partial("_Content", Model);
}
}

Related

Is it possible to pass variables from the controller to the view and create multiple different URLs?

I have only recently begun using MVC. I have a situation in my view where I need to create several different links to several different places. Currently I am using a switch statement using their type as a parameter, and writing them out individually. Example here:
switch (link.Type)
{
case "type1": %>
<a href='<%= Url.RouteUrl("A", new { controller = "Controller1", action = "Action1",
param1 = x, param2 = y, newWindow = "yes" }) %>' target="_blank"><%: link.Name %></a> <%
break;
case "type2": %>
<a href='<%= Url.RouteUrl("B", new { controller = "Controller2", action = "Action2",
param1 = x, param2 = y, newWindow = "yes" }) %>' target="_blank"><%: link.Name %></a> <%
break;
}
As you can see from the example above, there are only very minor changes between the URLs. I believe I will have 10-20 of these types, and obviously solution where I would only have this once on the page would be ideal.
Is it possible to pass in variables to replace "A", "Controller1", "Action1"?
EDIT1: Erik already nailed my question but out of curiosity, is it also possible to supply parameter names through variables as well? So instead of having a parameter called "param2" I could have "param2" or "param3" decided through a variable?
EDIT2:
x and y in the example are ints that the used when the Url.RouteUrl link is clicked.
So for example, I have my two parameters that are projectId and recordId in my application. When the user clicks a link they are taken to a different controller/view and proceed from there. projectId is consistent in all links, but sometimes instead of recordId it might be tableId, articleId, etc. Is there a way to deal with these different parameters?
MVC is a Acronym for Model, View and Controller. The Controller should more often then not, create a Model and pass it into the view:
So you might have a class like:
public class IndexViewModel
{
public string SomeString { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
}
Then a controller like:
public ActionResult Index()
{
var model = new IndexViewModel();
model.SomeString = "A";
model.Controller = "Controller1";
model.Action = "Action1";
return View(model);
}
Then the view:
#model IndexViewModel
<a href='<%= Url.RouteUrl(Model.SomeString,
new { controller = Model.Controller,
action = Model.Action,
param1 = x, param2 = y, newWindow = "yes" }) %>'
target="_blank"><%: link.Name %></a>
Update 1: You can't assign a value to a class in the constructor/initializer of an anonymous class.
var myvar = new MyClass;
var myanon = new { x = 1, MyClass.Prop = 2 };
//------------------------^
// You can't do this, it doesn't make sense
Update 2: The UrlHelper.RouteUrl() Method has a number of overrides. Instead of trying to dynamically create an object for the signature RouteUrl(String, Object) use the signature RouteUrl(String, RouteValueDictionary):
model:
public class IndexViewModel
{
public string SomeString { get; set; }
public RouteValueDictionary RouteValues { get; set; }
}
controller:
public ActionResult Index()
{
var model = new IndexViewModel();
model.SomeString = "A";
model.RouteValues = new RouteValueDictionary();
model.RouteValues.Add("Controller", "Controller1");
model.RouteValues.Add("Action", "Action1");
model.RouteValues.Add("param1", x);
model.RouteValues.Add("param2", y);
return View(model);
}
view:
#model IndexViewModel
<a href='<%= Url.RouteUrl(Model.SomeString,
Model.RouteValues) %>'
target="_blank"><%: link.Name %></a>
public ActionResult Index()
{
var model = new IndexViewModel();
ViewBag.Link1="A";
ViewBag.Link2="B";
ViewBag.Link13"C";
ViewBag.Link3="D";
return View("ViewName",model);
}
Inside View
//.Link1
if(ViewBag.Link1)
{
}
if(ViewBag.Link2)
{
}
if(ViewBag.Link3)
{
}
if(ViewBag.Link4)
{
}

MVC3 RadioButtonFor value is not binded to the model

I have a MVC3 Razor form. It have a radiobutton list and some another text fields. When I press submit controller post action get the view model, which have all fields seted correctly, except RegionID.
Model:
namespace SSHS.Models.RecorderModels
{
public class CreateViewModel
{
...
public int RegionID { get; set; }
...
}
}
Controller:
namespace SSHS.Controllers
{
public class RecorderController : Controller
{
...
public ActionResult Create()
{
EntrantDBEntities db = new EntrantDBEntities();
List Regions = new List(db.Region);
List Schools = new List(db.School);
List Settlements = new List(db.settlement);
CreateViewModel newEntr = new CreateViewModel();
ViewBag.Regions = Regions;
ViewBag.Schools = Schools;
ViewBag.Settlements = Settlements;
return View(newEntr);
}
[HttpPost]
public ActionResult Create(CreateViewModel m)
{
EntrantDBEntities db = new EntrantDBEntities();
Entrant e = new Entrant()
{
FatherName = m.FatherName,
Lastname = m.LastName,
LocalAddress = m.LocalAddress,
Name = m.Name,
RegionID = m.RegionID,
PassportID = m.PassportID,
SchoolID = m.SchoolID,
SettlementID = m.SattlementID,
TaxID = m.TaxID,
};
db.Entrant.AddObject(e);
db.SaveChanges();
return RedirectToAction("Index");
}
}
View:
#model SSHS.Models.RecorderModels.CreateViewModel
#using SSHS.Models
#using (Html.BeginForm("Create", "Recorder", FormMethod.Post))
{
#foreach (Region item in ViewBag.Regions)
{
#Html.RadioButtonFor(m => m.RegionID, item.RegionID)
#Html.Label(item.RegionName) - #item.RegionID
}
...
...
}
The Create(CreateViewModel m) method gets data from all textboxes normaly, but RegionID always is 0.
How are you planning to fill radio button with int ? It have two states: checked and not. Could you tell us, what are you trying to do? Make radio group? Use bool for RadioButtonFor.
Added:
You need to write something like this: CheckboxList in MVC3.0 (in your example you will have radio buttons)

Display Template For Generics - View is not found

I have the following classes:
public class Widget
{
public string Name { get; set; }
}
GenericModel
public class GenericModel<T>
{
public List<T> Data { get; set; }
}
My Controller action is:
public ActionResult Simple()
{
var model = new GenericModel<Widget>()
{
Data = new List<Widget>
{
new Widget {Name = "a"}
}
};
return View(model);
}
And my view is:
#model MyApp.GenericModel<MyApp.Widget>
#{
ViewBag.Title = "Simple";
}
<h2>Simple</h2>
#Html.DisplayFor(m=>m)
I have a file called GenericModel.cshtml in Views/Shared/DisplayTemplate folder:
#model MyApp.GenericModel<MyApp.Widget>
<ul>
#for (int i = 0; i < Model.Data.Count; i++ )
{
<li>
#Html.EditorFor(m=> Model.Data[i].Name)
</li>
}
</ul>
This view can not be found. I see when I print out the name of the type of my model I get "GenericModel1". Seeing that, I renamed my template "GenericModel1.cshtml". This seems like a bit of a hack, is there an easier way to find this display template without resorting to this?
You have to set it in your viewstart:
#Code
Layout = "~/Views/Shared/DisplayTemplate.cshtml"
End Code
Note: The above is VB.
You can also pass it via your controller like this:
public ActionResult Simple()
{
var model = new GenericModel<Widget>()
{
Data = new List<Widget>
{
new Widget {Name = "a"}
}
};
return View("", "DisplayTemplate", model);
}

MvcSiteMapProvider, DynamicNodeProviderBase and Globalization

I have the following class
public class MenuVeiculo
{
public string Nome { get; set; }
public string NomeEn { get; set; }
public Guid ID { get; set; }
}
As you can see, I have two properties, "Nome" and "NomeEn." Each one represents the name and the name in English.
Mvc.sitemap
<mvcSiteMapNode key="MenuVeiculo" dynamicNodeProvider="Semep.Extensibilidade.SiteMap.MenuVeiculoDynamicNodeProvider, Semep" title="Menu veiculo" action="Index" controller="Rental">
MenuVeiculoDynamicNodeProvider.cs
public class MenuVeiculoDynamicNodeProvider : DynamicNodeProviderBase
{
public override IEnumerable<DynamicNode> GetDynamicNodeCollection()
{
var context = DependencyResolver.Current.GetService<SemepContext>();
var listDB = (from p in context.MenusVeiculo
select new
{
p.Nome,
p.ID
});
const string keyFormat = "MenuVeiculo_{0}";
foreach (var menu in listDB.ToList())
{
var key = string.Format(keyFormat, menu.ID.ToString().ToUpper());
var root = new DynamicNode(key, menu.Nome)
{
Title = menu.Nome
,
Key = key
};
root.Attributes.Add("id", menu.ID.ToString());
root.RouteValues.Add("id", menu.ID);
yield return root;
}
}
#endregion
}
Question
My question is, how to work with multi-language and DynamicNodeProviderBase?
As you can see, there are two fields, and I'm only showing one.
A problem of "Thread.CurrentThread.CurrentCulture" is that the MvcSiteMapProvider caches the result, how to handle this:
Yes this is one of the shortcomings of MvcSiteMapProvider. In a project I've solved this by returning all the nodes, one for each localization, and using a custom VisibilityProvider to only show the correct localization.
You need to create an additional Route with lang parameter:
routes.MapRoute(
name: "Default_lang",
url: "{lang}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { lang = #"^(en|ru)$" },
namespaces: new[] { "PNSoft.WebSite.Controllers" }
);
Then, in your mvc.sitemap you specify lang="..." parameter for the root node and for child nodes you need to set inheritedRouteParameters="lang" and then you can get lang from node RouteValues property:
public override IEnumerable<MvcSiteMapProvider.DynamicNode> GetDynamicNodeCollection(MvcSiteMapProvider.ISiteMapNode node)
{
var lang = (string)node.RouteValues["lang"];
...
}
Thats all!

MVC3 Display a dropdown list from one datasource and save to another datasource

I'm getting back to an MVC3 project after a 3 month hiatus. I need to display a drop down list that pulls from Database A, but saves to Database B. The property I need to persist is the NAICS/SIC code. Right now I just provide the user a text box to key in freeform text. So, I have the mechanics of that down. But instead it should provide only a valid list of codes from a source database.
The tricky thing to is I'm using a custom model binder to generate my ViewModels on the fly, so I don't have a distinct .cshtml file to customize.
[Serializable]
public class Step4ViewModel : IStepViewModel
{
public Step4ViewModel()
{
}
//load naics codes from somewhere
[Display(Name = "Describe the nature of your business.")]
public String NatureOfBusiness { get; set; }
[Display(Name="NAICS/SIC CODE")]
public String BusinessTypeCode { get; set; }
Tricky ViewModel
#using Microsoft.Web.Mvc;
#using Tangible.Models;
#model Tangible.Models.WizardViewModel
#{
var currentStep = Model.Steps[Model.CurrentStepIndex];
var progress = ((Double)(Model.CurrentStepIndex) / Model.Steps.Count) * 100;
}
<script type="text/javascript">
$(function () {
$("#progressbar").progressbar({
value: #progress
});
});
</script>
<div id="progressbar" style="height:20px;">
<span style="position:absolute;line-height:1.2em; margin-left:10px;">Step #(Model.CurrentStepIndex + 1) out of #Model.Steps.Count</span>
</div>
#Html.ValidationSummary()
#using (Html.BeginForm())
{
#Html.Serialize("wizard", Model)
#Html.Hidden("StepType", Model.Steps[Model.CurrentStepIndex].GetType())
#Html.EditorFor(x => currentStep, null, "")
if (Model.CurrentStepIndex > 0)
{
<input type="submit" value="Previous" name="prev" />
}
if (Model.CurrentStepIndex < Model.Steps.Count - 1)
{
<input type="submit" value="Save & Continue" name="next" />
}
else
{
<input type="submit" value="Finish" name="finish" />
}
#*<input type="submit" value="Save" name="Save" />*#
}
Controller
[HttpPost]
public ActionResult Index([Deserialize] WizardViewModel wizard, IStepViewModel step)
{
wizard.Steps[wizard.CurrentStepIndex] = step;
if (ModelState.IsValid)
{
//Always save.
var obj = new dr405();
//wire up to domain model;
foreach (var s in wizard.Steps)
{
Mapper.Map(s,obj,s.GetType(), typeof(dr405));
}
using (var service = new DR405Service())
{
//Do something with a service here.
service.Save(db, obj);
}
if (!string.IsNullOrEmpty(Request["next"]))
{
wizard.CurrentStepIndex++;
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
else
{
return View("Upload", obj);
}
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
return View(wizard);
}
WizardViewModel
[Serializable]
public class WizardViewModel
{
public String AccountNumber { get; set; }
public int CurrentStepIndex { get; set; }
public Boolean IsInitialized { get { return _isInitialized; } }
public IList<IStepViewModel> Steps { get; set; }
private Boolean _isInitialized = false;
public void Initialize()
{
try
{
Steps = typeof(IStepViewModel)
.Assembly.GetTypes().Where(t => !t.IsAbstract && typeof(IStepViewModel).IsAssignableFrom(t)).Select(t => (IStepViewModel)Activator.CreateInstance(t)).ToList();
_isInitialized = true;
//rewrite this. get the profile and wire them up or something.
this.AccountNumber = Tangible.Profiles.DR405Profile.CurrentUser.TangiblePropertyId;
}
catch (Exception e)
{
_isInitialized = false;
}
}
}
You can specify a template for a specific property on your view model by adding the UIHint attribute to the field. Since your view calls EditorFor on the model it will use the template you specified with UIHint.
BusinessTypeDropdown.ascx - (placed in Views/Shared/EditorTemplates
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<string>" %>
<% var businessTypes = ViewData["businessTypes"] as IEnumerable<string>; %>
<%= Html.DropDownListFor(m => m , new SelectList(businessTypes, Model))%>
In your View Model
[Serializable]
public class Step4ViewModel : IStepViewModel
{
public Step4ViewModel()
{
}
//load naics codes from somewhere
[Display(Name = "Describe the nature of your business.")]
public String NatureOfBusiness { get; set; }
[Display(Name="NAICS/SIC CODE")][UIHint("BusinessTypeDropdown")]
public String BusinessTypeCode { get; set; }
Then in your controller just set ViewData["businessTypes"] to your list of business types.
Without understanding your "tricky" view model code, it will be hard to make helpful suggestions.
However, there shouldn't be much problem here. You need to somehow create your dropdown list in yoru view, and populate it from data passed from your controller.
All the work happens in your controller. Populate your list or IEnumerable or whatever data source from your first database, then in your post handler save the selection it to your second database (the second part should not be much different from what you already have).

Resources