ASP.Net Core2 Razor Selectlist - razor-pages

It seems that there is ZERO documentation related to a select/dropdown list in ASP.NET Core2 with Razor Pages.
I would like to create dropdown lists that select/display data from another table.
I am suspecting that each 'view' (Index, Details, Create and Edit) will most likely need a different implementation?
For example:
the Index and Details (which are View only) page, would need to read the value in the field, but then fetch that associated text from the reference table.
There is a ton of info out there that relates to an MVC but Razor pages don't have a ViewBag.
I have tried adding
var school = _context.AdmSchool.ToList();
SelectList selectList = new SelectList(school, "AdmSchoolId", "AdmSchoolName", null);
school = selectList;
in the create.cshtml.cs ('controller' for want of a Razor name) file
and
#Html.DropDownList("school", null, null, htmlAttributes: new{#class="form-control", placeholder="Select School"});
into the Create.cshtml (view) file.
So does anybody out there have experience with Razor pages?
I must confess that I am an absolute newbie, so the more verbose your response, the more I am likely to understand it.
Thanks

You should be looking to use the select tag helper in Razor Pages instead of the old-style Html helpers from earlier versions of MVC.
The data for the asp-items attribute should be a List<SelectListItem>:
var items = _context.AdmSchool.Select(s => new SelectListItem{
Value = s.AdmSchoolId.ToString(),
Text = s.AdmSchoolName
}).ToList();
You can assign this to a property on your PageModel (controller) or to ViewData (not ViewBag):
ViewData["Items"] = items;
Then you assign that to the asp-items attribute:
<select asp-for="SelectedAdmSchoolId" asp-items="#((List<SelectListItem>)ViewData["Items"])"></select>
Finally, add a property to the PageModel to capture the selected value:
[BindProperty]
public int SelectedAdmSchoolId {get; set;}

Related

How to return a different Razor Page without redirect?

I am porting a Asp.Net MVC application to Razor Pages.
In some of the controllers of the MVC application it makes use of return View("someOtherView", someModelForOtherView);
How do I port this to Razor Pages?
What I need to do is to transfer the request over to another Razor Page and pass the prepared PageModel to it (the other page does not need to execute OnMethod() but simply render its html.
Or, in other words, I only need to swap the template file that should be rendered with another one.
I cannot use Redirect as there must not be another roundtrip via the browser.
I doubt this is (easily) possible. From the github request that Lerner linked above, it's noted Razor Pages weren't designed to do that.
The closest workaround I was able to achieve was to turn my destination Razor Page into a View. (Hence, no code-behind.) Obviously that will only be possible if your destination page is never directly accessed via URL. For example, if you want to redirect to /Pages/MyPage, and you still need to be able to access the url http://example.com/MyPage, this won't work.
But, say all you want is a generic error or status page. Those don't have to be directly-accessible through URL. This works well for that.
Here's a couple extension methods on PageModel to do it, one that accepts models and one that doesn't:
public static ViewResult View(this PageModel pageModel, string viewName) {
return new ViewResult() {
ViewName = viewName,
ViewData = pageModel.ViewData,
TempData = pageModel.TempData
};
}
public static ViewResult View<TModel>(this PageModel pageModel, string viewName, TModel model) {
var viewDataDictionary = new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(), new ModelStateDictionary()) {
Model = model
};
foreach (var kvp in pageModel.ViewData) viewDataDictionary.Add(kvp);
return new ViewResult {
ViewName = viewName,
ViewData = viewDataDictionary,
TempData = pageModel.TempData
};
}
FYI, the reason for having to recreate the view dictionary is because the one in your pageModel is going to have a model type specific to the current Page, not to the View you're directing to, and you can't change the Model within a ViewDataDictionary to a different type. MVC would complain and throw an exception.
Usage:
public IActionResult OnGet(string id) {
// check if id is good here
if (idIsNoGood) return this.View("InvalidId", new ErrorModel...);
else {
return Page();
}
}
The above will look for InvalidId.cshtml view, which can be in the same folder as your page, the root /Pages/ folder, or /Pages/Shared/. And it'll still use your Layout too, like any other page.
Just make sure your cshtml file doesn't have a #page directive at the top; this won't work for a Razor page, only a View.
Example InvalidId.cshtml:
#model MyProject.Models.ErrorModel
<h1>Invalid Request</h1>
<p>#Model.Message</p>

ASP - EF 6 bind only certain properties of a complex list?

I have a view that creates one main object (Author) and a list of other objects for it (Books). So the created object can return the books by calling author.Books.ToList() for example.
My problem is that I only want users to be able to set certain attributes of the book (name, date etc.). I do not want them to be able to inject the form with javascript and set the Price of a book.
How do I tell the framework that I want to bind author.Books[all].Name (and date), but want to discard author.Books[all].Price?
I know I could just manually test it in the controller, but I felt like there is a better solution and I just can't quite put my finger on it.
Some code for context:
An inputbox from the View:
<input data-val="true" data-val-required="The CanBeBorrowed field is required." id="books_0__CanBeBorrowed" name="books[0].CanBeBorrowed" type="checkbox" value="true"`>
You can see how adding extra input fields would corrupt the data.
The controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name,Date,Books")]Author author {...}
(In my project, I have different classes with the same structure. That is why it looks silly creating all the books when creating the author.)
This exactly why we need to use ViewModels,
you can just set in ViewModel
[Editable(false)]
public decimal Price { get; set; }
and also in your
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([AuthorViewModel authorVm)
{
var author = _repository.getById(authorVm.Id);
//update only the fields of author object that user is allowed to update.
author.Name = authorVm.Name;
author.Date = authorVm.Date;
}
you can read more about ViewModels and how to use them
here and here

Orchard CMS front-end all possible content filtering by user permissions

Good day!
In my Orchard, I have several content types all with my custom part attached. This part defines to what users this content is available. For each logged user there is external service, which defines what content user can or cannot access. Now I need access restriction to apply everywhere where orchard display content lists, this includes results by specific tag from a tag cloud, or results listed from Taxonomy term. I seems can’t find any good way to do it except modifying TaxonomyServices code as well as TagCloud services, to join also my part and filter by it. Is this indeed the only way to do it or there are other solutions? I would like to avoid doing changes to built-in modules if possible but cannot find other way.
Thanks in advance.
I'm currently bumbling around with the same issue. One way I'm currently looking at is to hook into the content manager.
[OrchardSuppressDependency("Orchard.ContentManagement.DefaultContentManager")]
public class ModContentManager : DefaultContentManager, IContentManager
{
//private readonly Lazy<IShapeFactory> _shapeFactory;
private readonly IModAuthContext _modAuthContext;
public ModContentManager(IComponentContext context,
IRepository<ContentTypeRecord> contentTypeRepository,
IRepository<ContentItemRecord> contentItemRepository,
IRepository<ContentItemVersionRecord> contentItemVersionRepository,
IContentDefinitionManager contentDefinitionManager,
ICacheManager cacheManager,
Func<IContentManagerSession> contentManagerSession,
Lazy<IContentDisplay> contentDisplay,
Lazy<ISessionLocator> sessionLocator,
Lazy<IEnumerable<IContentHandler>> handlers,
Lazy<IEnumerable<IIdentityResolverSelector>> identityResolverSelectors,
Lazy<IEnumerable<ISqlStatementProvider>> sqlStatementProviders,
ShellSettings shellSettings,
ISignals signals,
//Lazy<IShapeFactory> shapeFactory,
IModAuthContext modAuthContext)
: base(context,
contentTypeRepository,
contentItemRepository,
contentItemVersionRepository,
contentDefinitionManager,
cacheManager,
contentManagerSession,
contentDisplay,
sessionLocator,
handlers,
identityResolverSelectors,
sqlStatementProviders,
shellSettings,
signals) {
//_shapeFactory = shapeFactory;
_modAuthContext = modAuthContext;
}
public new dynamic BuildDisplay(IContent content, string displayType = "", string groupId = "") {
// So you could do something like...
// var myPart = content.As<MyAuthoPart>();
// if(!myPart.IsUserAuthorized)...
// then display something else or display nothing (I think returning null works for this but
//don't quote me on that. Can always return a random empty shape)
// else return base.BuildDisplay(content, displayType, groupId);
// ever want to display a shape based on the name...
//dynamic shapes = _shapeFactory.Value;
}
}
}
Could also hook into the IAuthorizationServiceEventHandler, which is activated before in the main ItemController and do a check to see if you are rendering a projection or taxonomy list set a value to tell your content manager to perform checks else just let them through. Might help :)

Kentico 7 hide editable text if it's empty

I have an editable text web part on a page template. It has a custom HTML envelope before and after the text. How can I hide the whole thing, envelope included, if the editable text is empty?
I need to hide it because the envelope adds stylized markup that shouldn't be visible when there is no text.
Can it be done with a K# snippet on the Visible property? I'm unclear how interrogating a document's property works.
Thanks!
Try this as the "Visible" property:
{% (ViewMode != "LiveSite") || (CMSContext.CurrentDocument.editabletext != "") #%}
Change "editabletext" to whatever you have for your web part control ID.
I'm not familiar with Kentico but these solutions might help. They may not address your problem specifically but might aid in a solution.
CMSEditableImage Extension Method
I came up with a way to check this, I added an extension method for
the CMSEditableImage class that takes the CurrentPage PageInfo object
to check the value of the editable region, don't know if this is the
best way or not, but here's the code.
public static bool IsPopulated(this CMSEditableImage editableImage, PageInfo currentPage)
{
bool isPopulated = false;
string value = currentPage.EditableItems.EditableRegions[editableImage.ID.ToLower()].ToString();
if (!string.IsNullOrEmpty(value))
{
value = value.ToUpper();
isPopulated = (value == "<IMAGE><PROPERTY NAME=\"IMAGEPATH\"></PROPERTY></IMAGE>") ? false : true;
}
return isPopulated;
}
via http://devnet.kentico.com/Forums/f19/fp5/t4454/Empty-CMSEditableImage.aspx
JavaScript Method
The webcontainer needs an id, example:
<h2 id="webpart-header">Headline</h2>
Then I have a small javascript function that is attached in an
external js file:
/* Hide Webcontainer via javascript if empty*/
function hideLayer(element) {
elem = document.getElementById( element );
elem.style.display = "none";
}
Now in the wep part configuration, at no data behaviour, you uncheck the checkbox and call the js function by entering following
script in the no record found text: hideLayer("webpart-header");
Whereby webpart-header the id name of your container is. You could
also have a more complex <div> structure here.
via http://devnet.kentico.com/Forums/f22/fp3/t4180/Webcontainer-and-hide-if-no-data.aspx

Getting Content Types in Orchard CMS

I have created a View using module, now in controller of this view i need to fetch some specific content type and return to view. Please can some one eleborate with code sample.
You will need to inject the IContentManager services in your controller constructor (see dependency injection) , but since you will need to populate a new shape, you could inject IOrchardServices which will include a few common OrchardServices in one instance.
IOrchardServices services;
public MyController(IOrchardServices services){
this.services = services;
}
Then in your action (if you want to show it on the front end you will have to mark it as themed), do something like this:
[Themed]
public ActionResult MyAction(){
//Notice that you can filter the contentItems here, this is just a basic example
var myContentItems = services.ContentManager.Query().ForType("MyContentItem").List();
//You probably need to create a new shape for showing the ContentTypes
var shape = services.New.YourCustomShape(); //Notice that you must create a view that matches this name
shape.YourContentItems = myContentItems;
return new ShapeResult(this, shape);
}
And that's it.

Resources