I have a class that contains a super object. When creating this class, it will contain one of the subobjects of the superobject. However, because the attributes of the subobjects are not in the superobject, I cannot access these attributes in Razor. Can someone tell me how I can reach the subobjects' attributes? I cannot put the subobjects' attributes in the superobject, because I'm going to convert the class to json and I cannot have all attributes visible in the json.
Class:
public class FoundPattern
{
public Pattern Pattern = new Pattern();
}
Superobject:
public class Pattern : OntologicalModel
{
}
Subobjects:
public class KpiPattern : Pattern
{
public List<KPI> KPIs = new List<KPI>();
}
.
public class ProcessPattern : Pattern
{
public List<Process> Processes = new List<Process>();
}
Razor page:
#model IEnumerable<FoundPattern>
#foreach (var x in item.SUBOBJECTNAME.SUBOBJECTATTRIBUTE)
{
// do something
}
Instead of SUBOBJECTNAME and SUBOBJECTATTRIBUTE I need the subobject and subobject attribute, respectively.
Should be something like
#foreach (var x in Model)
{
if(x.Pattern is ProcessPattern) {
foreach (var y in ((KpiPattern)x.Pattern).Processes)
{
//enter code here
}
}
if(x.Pattern is KpiPattern) {
foreach (var y in ((KpiPattern)x.Pattern).KPIs)
{
//enter code here
}
}
}
If you don't have intellisense or the view is showing syntax errors then a possible issue is that you didn't include the namespace of your classes in your web.config in your Views folder.
Remember to open and close the view after editing the web.config for the intellisense to start working correctly.
Related
I am eventually trying to create a nav menu that can auto matically populate itself with the sites pages. I need to get a list of all the endpoints and store them to a database to be accessed by the logic (this will just be .txt or .json for now).
In my original post here: https://stackoverflow.com/questions/74988601/how-can-i-can-get-a-list-of-razor-pages-in-a-razor-pages-app I was able to get a list of all endpoints with a constructor, but unable to access these variables from anywhere but that specific razor page view. Thanks to Md Farid Uddin Kiron.
I tried simply copying the list to a variable in another class ("endpointStringTest" in the "JSONTest" class):
public class IndexModel : PageModel
{
public readonly IEnumerable<EndpointDataSource> _endpointSources;
public IndexModel(IEnumerable<EndpointDataSource> endpointDataSources)
{
_endpointSources = endpointDataSources;
}
public IEnumerable<RouteEndpoint> EndpointSources { get; set; }
public void OnGet()
{
EndpointSources = _endpointSources
.SelectMany(es => es.Endpoints)
.OfType<RouteEndpoint>();
foreach(var endpointSource in EndpointSources)
{
Console.WriteLine(endpointSource);
Debug.WriteLine(endpointSource.ToString());
JSONTest.endpointStringTest.Add(endpointSource.DisplayName);
}
Console.WriteLine(JSONTest.endpointStringTest);
Debug.WriteLine(JSONTest.endpointStringTest);
}
}
But this results in a null reference. If i understand correctly, this is due to constructors being initialized and deleted before normal classes are initialized? is there a way to work around this?
I also tried turning the above constructor into a regular method, but the variables were always null. I don't fully understand where "endpointDataSources" is getting it's value. It's obviously something to do with being initialized within a constructor, as thats the only time it's not null.
By writing the endpoints to a text file (or theoretically any kind of database) i can simply pass the info via that text file.
Index model: Gets a list of all page paths and then writes them to a text document. Obviously in practice you would want to format this as .json, .xml or to a database etc..
public class IndexModel : PageModel
{
public readonly IEnumerable<EndpointDataSource> _endpointSources;
public IndexModel(IEnumerable<EndpointDataSource> endpointDataSources)
{
_endpointSources = endpointDataSources;
}
public IEnumerable<RouteEndpoint> EndpointSources { get; set; }
public void OnGet()
{
EndpointSources = _endpointSources
.SelectMany(es => es.Endpoints)
.OfType<RouteEndpoint>();
//string filePath = App.enviroment;
StreamWriter writer = new StreamWriter("pagesTest.txt");
foreach (var endpointSource in EndpointSources)
{
Console.WriteLine(endpointSource);
Debug.WriteLine(endpointSource.ToString());
writer.WriteLine(endpointSource.ToString());
}
writer.Close();
}
}
PageListGetter model: this copies the contents of the previously created document and stores them in a variable.
public class PageListsModel : PageModel
{
public string? pageList;
public void OnGet()
{
StreamReader reader = new StreamReader("pagesTest.txt");
pageList = reader.ReadToEnd();
reader.Close();
}
}
I have also tested this on an Azure published version of the site and it works fine. I was concerned the filepaths may not line up or may be inaccessible from a regular model.
Something strange is happening in my umbraco project where I have a repository set up like so;
public class HireItemsRepo:BaseGenericRepository<YouHireItContext,HireItem>
{
public List<HireItemViewModel> PopulateHireItemViewModel(RenderModel model)
{ List<HireItemViewModel> HireItems = new List<HireItemViewModel>();
foreach (var Hireitem in base.GetAll())
{
HireItems.Add(
new HireItemViewModel(model.Content)
{
Title = Hireitem.Title,
Price = Hireitem.Price
}
);
}
return HireItems;
}
}
which I'm using in my controller like this
public class HiresController : RenderMvcController
{
// GET: Hire
public override ActionResult Index(RenderModel model)
{
HireItemsRepo repo = new HireItemsRepo();
var VM = repo.PopulateHireItemViewModel(model);
return View("Hires",VM.ToList());
}
}
And using that model in the view like this;
#model List<You_Hire_It.Models.HireItemViewModel>
/*HTML starts here*/
It's strange because if I try to use that model as a List, Umbraco will blow up with the following error;
Cannot bind source type System.Collections.Generic.List`1[[You_Hire_It.Models.HireItemViewModel, You_Hire_It, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] to model type Umbraco.Web.Models.RenderModel.
However, if I refactor all the code to use the model on it's own as if I only have one set of values to use, it has no problem with it!
Could anybody point me in the right direction with this please?
Many thanks in advance!
You can inherit from RenderModel as DZL suggests. However, I generally prefer to use route hijacking which would enable me to keep my models simple.
Instead of the Index method in your RenderMvcController, you can create a method with the same name as your view. I note your view is called Hires. So change your controller code to this:
public class HiresController : RenderMvcController
{
// GET: Hire
public ActionResult Hires(RenderModel model)
{
HireItemsRepo repo = new HireItemsRepo();
var VM = repo.PopulateHireItemViewModel(model);
return CurrentTemplate(VM)
}
}
You now need to have your view inherit from UmbracoViewPage. So at the top of your view replace the #model line with the following:
#inherits UmbracoViewPage<List<HireItemViewModel>>
Your model in the view is now of type List<HireItemViewModel> which I think is what you want.
So to iterate the items you would use:
#foreach(var item in Model){
{
// etc
}
Additionally, as this view now inherits from UmbracoViewPage, you have access to the UmbracoContext - just use #Umbraco
For example:
#Umbraco.TypedContentAtRoot().Where(x=>x.DocumentTypeAlias == "HomePage")
or
#Umbraco.AssignedContentItem etc
That is because the model you return from the action need to be of type RenderModel or inherit from it and in your case you are returning a List.
So your model should look something like this:
public class ViewModel : RenderModel
{
public ViewModel(IPublishedContent content) : base(content) { }
public List<HireItem> HireItems { get; set; }
}
public override ActionResult Index(RenderModel model)
{
var vm = new ViewModel(model);
vm.HireItems = new HireItemsRepo().GetHireItems();
return View("Hires", vm);
}
I'm having a problem with a polymorphic collection of ViewModels in my MVC application. I received this via a web service call and i need to iterate through them and give them their own partial view, based on the object type.
public abstract class ProvinceViewModel
{
public string Code { get; set; }
}
public sealed class OntarioViewModel : ProvinceViewModel { }
public sealed class QuebecViewModel : ProvinceViewModel {}
In my view i am trying to iterate through them and assign a partial view. I have to do a lot of type casting here to make it work. If I try and move this to a controller action and pass in the abstract type, i will get an error that we cannot create an instance of abstract class.
ICollection<ProvinceViewModel> ProvinceList; // collection receive via service
#for (int i = 0, c = ProvinceList.Count; i < c; i++)
{
var currentProvince = this.Model.ElementAt(i);
#switch (additionalRegistry.Code)
{
case "QC":
#Html.Partial("AlbertaDetail", (QuebecViewModel)currentProvince)
break;
case "ON":
#Html.Partial("OntarioDetail", (OntarioViewModel)currentProvince)
break;
default:
#Html.Partial("ProvinceDetail", ProvinceViewModel)
break;
}
}
I have strongly type View, so that i can access the different properties.
How would i go about solving this in a more elegant way? Would I need to create a new surrogate base class for the abstract class to create a instance of it easier?
You can achieve this with display templates. Create a display template for each type in the DisplayTemplates folder within your Controller's Views directory:
+-- Views
+-- Provinces
+-- DisplayTemplates
+-- OntarioViewModel.cshtml
+-- QuebecViewModel.cshtml
Display each model using the DisplayFor helper in your view:
#model ICollection<ProvinceViewModel>
#foreach (var province in Model)
{
#Html.DisplayFor(_ => province)
}
Upon encountering the same problem in the past, I have created the following solution:
First, decorate your (concrete) view-model with ExportMetadata attribute that denotes the view name to be used. For example:
[ExportMetadata("View", "Ontario")]
public sealed class OntarioViewModel : ProvinceViewModel { }
[ExportMetadata("View", "Quebec")]
public sealed class QuebecViewModel : ProvinceViewModel {}
Then extend your HtmlHelper with the following Partial method:
public static MvcHtmlString Partial<T>(this HtmlHelper htmlHelper, T model, string prefix = null)
{
var modelType = typeof (T);
var partialAttr = modelType.GetCustomAttributes<ExportMetadataAttribute>().SingleOrDefault(x => x.Name == "View");
if (partialAttr == null)
throw new Exception(modelType.Name + " doesn't define any view to be used");
var partialName = (prefix ?? String.Empty) + partialAttr.Value;
return htmlHelper.Partial(partialName, model, htmlHelper.ViewData);
}
Then use it:
#Html.Partial(currentProvince);
And in case your partials reside in some sub-directory:
#Html.Partial(currentProvince, "Partials/")
(If you need help registering the custom HTML helper see https://stackoverflow.com/a/5052790)
I had a similar requirement and this is how I managed to solve this issue.
My viewmodel (BusinessEventEmailViewModel ) has a list of interfaces (IBusinessEventEmail) resolved at runtime with unity. A IBusinessEventEmail has an EventCode property.
public class BusinessEventEmailViewModel : MailHeaderViewModel
{
#region members
public List<IBusinessEventEmail> Events { get; set; }
In my view, I render the partial view using a naming convention :
Html.RenderPartial("~/Views/Shared/Email/_" + businessEvent.EventCode + ".cshtml", businessEvent);
Then, I have a XXXEventEmail implementing IBusinessEventEmail with the EventCode XXX and a partial view _XXX.cshtml
I have created razor forms that do a #foreach to loop over the model and produce my rows. Now I am trying to to the same thing over a ViewModel, and I get the following error.
Unable to create a constant value of type 'MyProject.Models.SupportContact'.
Only primitive types or enumeration types are supported in this context.
My #model is being passed in as IQueryable(MyProject.Models.SupportContactFormView), and SupportContact is part of that ViewModel, but I can't seem to reference it in the model.
I can do this:
#foreach (var item in Model ) {
#Html.DisplayFor(modelItem => item.SupportContact.Contact)
But then I will get the error above when it actually try to process my cshtml. I have tried to do:
#foreach (MyProject.Models.SupportContact item in Model ) {}
But I can't drill down thru the model to get the SupportContacts. How can I get to a my SupportContact class inside my ViewModel?
Thanks
Edit:
Here is the ViewModel declaration:
public class SupportContactFormView
{
public SupportContact SupportContact { get; private set; }
public SelectList Priorities { get; private set; }
public SelectList ContactTypes { get; private set; }
public String Group_COde { get; private set; }
}
In your case, I would create a viewmodel specifically for that view and set it up like so
ViewModel Class
public class IndexViewModel
{
public IList<SupportContact > SupportContacts { get; set; }
}
Controller
public ActionResult Index()
{
var viewModel = new IndexViewModel();
viewModel.SupportContacts = IQueryableListOfContacts.ToList();
return View(viewModel)
}
View
#model IndexViewModel
#foreach (var contact in Model.SupportContacts) {
#Html.DisplayFor(_ => contact.Contact)
You need to play around a bit with the code, as I doubt I have the property names/action method name correct first time, but I hope you get the basic idea. Create a viewmodel class that the view file makes use of, and dump the contact details into a List. That will make it easier for you.
EDIT:
Actually, I think I got the wrong end of the stick here. The error "Unable to create a constant value of type 'MyProject.Models.SupportContact'.
Only primitive types or enumeration types are supported in this context." means that there is something wrong with your LINQ data.
Are you using Entity Framework or Linq2SQL? Ignore my answer about the viewmodel appraoch, your actual issue is LINQ related. Try iterating over the list of contacts in an ActionMethod instead e.g.
public ActionMethod Index()
{
foreach (var contact in whateveryourcontactslistis) {
}
// ....
}
If you still have a problem here, then it's your LINQ data at fault.
JSON.NET deserializes it fine, but whatever mvc uses for controller parameter binding barfs hard. Can I do anything else to make this work?
The bits:
public partial class Question
{
public Dictionary<string, List<QuestionExtendedProp>> TemporaryExtendedProperties { get; set; }
}
And the controller method
[HttpPost]
public JsonResult SaveQuestions(Question[] questions)
{
var z =
JsonConvert.DeserializeObject(
"{'Options':[{'PropKey':'asdfasd','PropVal':'asdfalkj'},{'PropKey':'fdsafdsafasdfas','PropVal':'fdsafdas'}]}",
typeof (Dictionary<string, List<QuestionExtendedProp>>)) as Dictionary<string, List<QuestionExtendedProp>>;
//this deserializes perfectly. z is exactly what I want it to be
//BUT, questions is all wrong. See pic below
//lots of code snipped for clarity, I only care about the incoming questions object
return Utility.Save(questions);
}
Here's what MVC gives me for this exact string (Pulled from fiddler, extras snipped for your reading pleasure)
"TemporaryExtendedProperties":{"Options":
[{"PropKey":"NE","PropVal":"NEBRASKA"},
{"PropKey":"CORN","PropVal":"CHILDREN OF"},
{"PropKey":"COW","PropVal":"MOO"}]}
Why does MVC mangle the binding from this perfectly fine json string and how can I get it to not do so? I have complete control over the json structure and creation.
Edit
I tried changing the type of Question.TemporaryExtendedProperties to List<KeyValuePair<string, List<QuestionExtendedProp>>>, but that didn't work either. Here's the new json (which matches exactly what System.Web.Script.Serialization.JavaScriptSerializer serializes an object to!)
{
TemporaryExtendedProperties: [
{
Key: 'Options',
Value: [
{
PropKey: 'NEBRASKA',
PropVal: 'NE'
},
{
PropKey: 'DOG',
PropVal: 'CORN'
},
{
PropKey: 'MEOW???',
PropVal: 'COW'
}
]
}
]
}
That didn't work either. It's deserialized by the controller to a List<blah,blah> properly, with a count of 1 (as expected), but the Key and Value are both null. Json.NET again handles it perfectly.
Ugh.
I ended up just removing the need for a dictionary. The new code looks like this:
//Other half is autogenerated by EF in models folder
public partial class Question
{
public List<QuestionExtendedProp> TemporaryExtendedProperties { get; set; }
}
//Other half is autogenerated by EF in models folder
public partial class QuestionExtendedProp
{
public string DictionaryKeyValue { get; set; }
}
mvc handles this just fine. My controller now looks like this
[HttpPost]
public JsonResult SaveQuestions(Question[] questions)
{
foreach (var q in questions)
{
//do regular question processing stuff
//20 lines later
IEnumerable<IGrouping<string, QuestionExtendedProp>> ExtendedPropGroups = q.TemporaryExtendedProperties.GroupBy(x => x.DictionaryKeyValue);
foreach (IGrouping<string, QuestionExtendedProp> group in ExtendedPropGroups)
{
string groupKey = group.Key;
foreach (var qexp in group)
{
//do things here
}
}
}
//rest of the stuff now that I've processed my extended properties...properly
return Utility.SaveQuestions(questions);
}