How can I weld part to an admin page in Orchard CMS - c#

In Orchard CMS, I can create a part and weld it to the site using Filters.Add(new ActivatingFilter<TermsAndConditionSettingsPart>("Site")); and have the editor for this part show up in the site settings.
I also have a few pages in my admin screens that I have used controllers and actions to allow the user edit settings for my modules.
I am wondering how I can weld a part onto one of my custom admin pages. I think I need to do something similar to the code mentioned above, but I'm not too sure what I should be welding to (ie- what should I replace "Site" with)? Do I need to create a content type for each of my admin pages?
Any help would be appreciated.

After exploring further and taking into account #Piotr's excellent answer, I've managed to achieve what I wanted to do.
Step 1: Migrations
private readonly IOrchardServices _services;
public Migrations(IOrchardServices services) {
_services = services;
}
public int Create()
{
ContentDefinitionManager.AlterTypeDefinition("PlayerSearch", cfg => { });
var content = _services.ContentManager.New("PlayerSearch");
_services.ContentManager.Create(content);
return 1;
}
In the example above, "PlayerSearch" is the name of my content type (ie the item I will be welding my parts to). This code simply creates a PlayerSearch type and creates a single instance of it which is then persisted.
Step 2: ContentPart
I created a simple POCO class as a ContentPart. This is what I want to weld to my PlayerSearch page:
public class PlayerSearchPart : ContentPart
{
public string String1 { get; set; }
public string String2 { get; set; }
public string String3 { get; set; }
public int Int1 { get; set; }
public int Int2 { get; set; }
public int Int3 { get; set; }
}
Step 3: ContentHandler
The next thing I did, was to weld my PlayerSearchPart to my PlayerSearch type as defined in the migrations:
public class PlayerSearchHandler : ContentHandler
{
public PlayerSearchHandler()
{
Filters.Add(new ActivatingFilter<PlayerSearchPart>("PlayerSearch"));
}
}
I did this in the ContentHandler by using the ActivatingFilter.
Step 4: The Controller
Now we need to create a page in the admin screens that is capable of displaying all the welded parts to the user:
private readonly IOrchardServices _services;
public PlayerManagementController(IOrchardServices services) {
_services = services;
}
[HttpGet]
public ActionResult PlayerSearch()
{
var playerSearchType = _services.ContentManager.Query().ForType(new[] {"PlayerSearch"}).Slice(0, 1).FirstOrDefault();
var model = _services.ContentManager.BuildEditor(playerSearchType);
// Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
return View((object)model);
}
This action retrieves the instance of my PlayerSearch type that I created in the Migrations file, builds the editor for it and then passes this to the view.
Step 5: The View
Our action of course needs a view, and it's pretty simple once you know how:
#using (Html.BeginFormAntiForgeryPost()) {
#Html.ValidationSummary()
#Display(Model.Content)
<fieldset>
<button class="primaryAction" type="submit">#T("Save")</button>
</fieldset>
}
Step 6: The Driver
Nothing out of the ordinary here:
// GET
protected override DriverResult Editor(PlayerSearchPart part, dynamic shapeHelper)
{
return ContentShape("Parts_PlayerSearch_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/PlayerSearch",
Model: part,
Prefix: Prefix));
}
// POST
protected override DriverResult Editor(PlayerSearchPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
Step 7: Displaying the editor for your part
The final step here is to create the editor template for your part, and to also ensure that you have a record in your placement.info for the part so that Orchard knows where to render it.
This technique was taken from the way that the site settings work. To have a look at this, and to see how the Post action would work too, have a look at the controller at Orchard.Core.Settings.Controllers.AdminController.

You cannot weld anything onto a custom page - it doesn't work like this.
Parts are welded onto content items, like Site. Site is no different from other content items, like User, Page etc., with one exception - there is always a single Site item per tenant. What you actually see when you go to any section in Settings is an editor for that item (and each section displays a part of that editor that corresponds to a named group). And this functionality is specific for Site items only.
If you need site-wide settings, the best way is to always weld parts onto Site item. Then you can provide links (or tabs etc.) that point to that specific editor from inside your custom views.

Related

How can I add different controls dynamically to a web page using asp.net core?

I am new to .net core - have been using aspx web pages and .net framework 4.x for a number of years. I have a project where we want to display different controls (textbox, dropdown, checkbox) on the page based on values returned from a query. For example, user chooses "A" from a dropdown list and it shows 10 controls, if they choose object B it shows 8 controls, etc. Previously in .net framework, I would use a content placeholder with an ID and then find that ID and start adding controls (controls.Add(newControl)) in the placeholder. It doesn't seem that is an option with .net core. It seems like this would be a common need for various web applications, but I'm not finding many hits.
Another question is whether this can be done in the code behind or if it has to be done on the client-side. If one of the controls in the list is a dropdown, there will be a query that a subroutine will run to get the Key/Value pairs for the dropdown. To me this means it would be more effective on the server side.
I haven't really found any good examples when I do some searching. Can anyone point me to a good resource or provide me with a basic example - either client-side or server-side? Thanks!
There are many options, but I'll describe a simple one, using server side processing. As you explained in your comment, there will be 2 pages:
One that will display the select element that will be used to choose a set of controls.
The page that will be returned according to the previous choise, displaying the selected set of controls.
I assume that you know how to build the first page.
For the second page, you can leverage the ASP.NET Core MVC pattern to achieve the desired result.
You will need the three usual MVC elements:
An Action in a Controler.
A ViewModel for your Razor View.
A Razor View.
The Action does the following:
Receives the id of the selected set of control (via the Action's parameter).
Uses this id to retrieve the information about the corresponding set of controls from your repository.
Builds a ViewModel out of the received information.
Builds a View using the obtained ViewModel.
Return the builded View.
Here is some simplified example code:
In your controller, add the following method:
#!lang-cs
Public IActionResult GetProgramControlSet(int ProgramId)
{
// Here, use the id to get the data from your repository
// that will be used to build set of controls.
// Supposing you have defined a GetControls method,
// it could look like:
var SelectedControls = MyRepository.GetControls(ProgramId);
// If needed, you can build a ViewModel out of the received SelectedControls.
var SelectedControlsViewModel = new ControlSetViewModel(SelectedControls);
return View(SelectedControlsViewModel)
}
Of course, many things are missing here: error handling, etc...
Here is what the ViewModel could be:
#!lang-cs
public class ControlSetViewModel
{
public string Name { get; private set; }
public List<IControl> Controls { get; private set; }
public ControlSetViewModel(...)
{
// Whatever needs to be done to construct the ViewModel
}
}
public enum ControlKind
{
Button,
Select,
Textarea
//...
}
public interface IControl
{
ControlKind Kind { get; }
}
public class ControlButton : IControl
{
public ControlKind Kind => ControlKind.Button;
public string Label { get; set; }
public string Text { get; set; }
public string Color { get; set; }
// ... All other needed properties for the button
}
public class ControlTextarea : IControl
{
public ControlKind Kind => ControlKind.Textarea;
public string Label { get; set; }
public string PlaceholderText { get; set; }
public string RowCount { get; set; }
// ... All other needed properties for the textarea
}
public class ControlSelect : IControl
{
public ControlKind Kind => ControlKind.Select;
public string Label { get; set; }
public string PlaceholderText { get; set; }
public List<SelectOption> Options { get; set; }
// ... All other needed properties for the select
}
public class SelectOption
{
public string Text { get; set; }
public string Value { get; set; }
}
You could also use inheritance instead of interface for the control classes.
Now the view.
It is a Razor page containing something akin to
#model ControlSetViewModel
#*... some HTML ...*#
<div>
<h1>#Model.Name</h1>
#foreach(var control in Model.Controls)
{
<div>
switch(control.GetControlKind())
{
case ControlKind.TextArea:
var Textarea = (ControlTextarea)control;
<label>#Textarea.Label</label>
<textarea rows="#Textarea.RowCount"/>
break;
case ControlKind.Select:
var Select = (ControlSelect)control;
<label>#Select.Label</label>
<select>
#foreach(var option in Select.Options)
{
<option value="#option.Value">#option.Text</option>
}
</select>
break;
#*... etc ...*#
default:
#*... etc ...*#
}
</div>
}
</div>
#*... More HTML ...*#
Of course this is far to be finished. All the infrastructure and code that will actually react to the displayed controls is missing.
Is it a form you that will be posted?
Is it Javascript code that will react to the control manipulation?
Or another mecanism?
This questions will need to be addressed.

Share a single Razor Create View with a set of Models that have the property in a scalable way

In MVC C# Web Applications with Razor, I constantly end up wanting to reuse View code for Create actions.
Imagine this scenario:
public class Taco
{
public Lunch Lunch { get; set; }
public Supper Supper { get; set; }
public string Cheese { get; set; }
}
public class Lunch
{
public IEnumerable<Taco> Taco { get; set; }
}
public class Supper
{
public IEnumerable<Taco> Taco { get; set; }
}
You have Lunch and Supper that have Tacos.
Now take these two use cases:
From Supper's Details View
Want to add a Taco
Click 'Create New Taco'
Enter Taco information
Click 'Create' Button
Redirected to Supper Details with new Taco there
From Lunch's Details View
Want to add a Taco
Click 'Create New Taco'
Enter Taco information
Click 'Create' Button
Redirected to Lunch Details with new Taco there
What is a scalable and MVC-Correct way to do this?
I have always felt like my process for this is hacked together and not very scalable. I might do something like:
Supper View:
#Url.Action("Create", "Taco", new { From = "Supper" })
Lunch View:
#Url.Action("Create", "Taco", new { From = "Lunch" })
Then take the "From" variable and pass it to
Taco Controller>Taco View Model>Taco View>Link Back To From
Is there a built in way to pass referrer information and is there a pre-defined design template for MVC to handle these situations?
Just literally reuse everything. For example, you can have just one action with one view and use the URL to determine behavior. All you need is a view model so the form can work with just one class type, and then you can map the data onto wherever it should go. For example:
[HttpPost]
[ValidateAntiForgeryToken]
[Route("{mealType:regex(supper|lunch)}/create")]
public ActionResult CreateMeal(string mealType, MealViewModel model)
{
if (ModelState.IsValid)
{
switch (mealType)
{
case "supper":
// map data to new `Supper` and save
break;
case "lunch":
// map data to new `Lunch` and save
break;
}
// do redirect
}
return View(model);
}
There's other ways to handle this without using Attribute Routing, but the general idea is that in some form or fashion you indicate which type of meal is being saved, and branch accordingly, creating and saving the appropriate class.
As far as I know there is no pre-defined template. But you can create a EditorTemplate if you want one and widely used in your razor views.
Also, instead of sending From in route parameters, you can have a property in Supper and Lunch classes like gobackUrl (just example) and generate gobackUrl in Create GET action and have it in hidden form value. So, the child action view will be more generic and you don't need to have if-else logic in parent view.

How to combine database access and cache in asp.net mvc (An object reference is required for the non-static field, method, or property 'Module.dbApp')

This is actually 2 questions in one.
I have an asp.net mvc application where I have to load a list of Modules, its just a simple list with ID, modulename and a class name to render it on the view with font awesome.
My model is like this:
public class Module
{
[Key]
public int Id { get; set; }
public string ModuleName { get; set; }
public string FontAwesomeClass { get; set; }
}
Because the module list is a Partial View that will render some icons on the top navigation bar, I dont want that for each refresh of the app, it goes to the DB, so it must be cached(I am using Azure REDIS Cache, not relevant for the question anyway), so instead of calling the DB context directly from the controller, I am calling a Cache Class that will check if the cache object exists, if not it will retrieve it from DB, if it does, it will return it from cache.
This my solution structure:
http://screencast.com/t/uayPYiHaPCav
Here is my controller Module.cs
public ActionResult GetModules()
{
return View(Cache.Module.GetModules());
}
As you can see the Controller does not have any logic where to get the data from.
Here is the Module.cs (on the Cache Namespace)
public class Module
{
private AppDataContext dbApp = new AppDataContext();
//Load modules from cache or from database
public static List<Models.Module> GetModules()
{
IDatabase cache = Helper.Connection.GetDatabase();
List<Models.Module> listOfModules = (List<Models.Module>)cache.Get("Modules");
if (listOfModules == null)
{
return dbApp.ModuleList.ToList();
}
else
{
return listOfModules;
}
}
}
Here I have a compiler error which I am not sure how to best fix it:
Error CS0120 An object reference is required for the non-static field,
method, or property 'Module.dbApp'
So that was my first question.
The 2nd question is more about the design pattern, do you consider this correct or not? the way I am trying to get the data from Cache, and its actually the Cache class which checks if data is on it or if it has to go to the DB.
First Question: make your private member static
private static AppDataContext dbApp = new AppDataContext();
2nd Question: your cache strategy seems pretty standard. The only thing is that you might want to expire cache data. For example, the cached data can get old and the longer it stays in the cache the older it gets. You might at some point want to expire it and get fresh data again.
Update:
#EstebanV for code sample (this off the top of my head, don't assume that it compiles):
/**
ICachedPersonDao abstracts away the caching mechanism
away from the core of your application
**/
public CachedPersonDao : ICachedPersonDao
{
private IPersonDao personDao = null;
public CachedPersonDao(IPersonDao personDao)
{
this.personDao = personDao;
}
public Person GetPersonById(int id){
bool isInCache = CACHE.SomeFunctionThatChecksInYourCache(id);
if (isInCache)
{
return CACHE.SomeFunctionThatReturnsTheCachedPerson(id);
}
else
{
//Well it's not in the cache so let's get it from the DB.
return this.personDao.GetPersonById(id);
}
}
}
/**
IPersonDao abstracts database communication
away from the core of your application
**/
public class PersonDao : IPersonDao
{
public Person GetPersonById(int id)
{
/** Get the person by id from the DB
through EntityFramework or whatever
**/
}
}
Usage:
In your controller, use ICachedPersonDao if you want to attempt to get from cache or use IPersonDao if you want to get it directly from the database without checking the cache.
Like I said, you should learn Dependency Injection it will help "inject" these dependencies into the classes that uses them.
I say again, this is off the top of my head. It won't compile. It's just to illustrate the concept.

ASP.NET MVC - Better UX approach to allowing child object editing than For loop and Partial Views

I have a Contact object that has a number properties, including a child that is a list of Addresses.
public class Contact
{
public int? Id { get; set; }
public string Name { get; set; }
public IReadOnlyList<IAddress> Addresses
{
[Lazy Load Code to populate and return list]
}
[...]
}
I want to allow the user to edit the addresses without having to edit (or post) the whole Contact object. Currently in the UI I have the addresses listed out with an Edit button next each one:
I'm using the Modal syntax that is part of Bootstrap3, and have hidden DIVs that contain a form and the fields to edit an Address. When the user clicks on edit, a modal window form appears that allows the user to edit the Address. Model Binding and validation work within this form.
It works pretty well but I have a few underlying issues with the implementation. I wanted to use the builtin Validation and Model Binding with the Address objects, but I didn't want to post back the whole object just to edit one address.
So I ended up having to create a for loop that writes out the hidden DIVs calling a Partial View and passing Address as the model:
#for (int i = 0; i < Model.Addresses.Count; i++)
{
address = (Address)Model.Addresses[i];
#Html.Partial("_AddressModal", address);
}
The unfortunate side-effect is that the model binding cannot uniquely identify which Address to apply the ModelState to, so Model Binding applies it to all the Address in in the hidden DIVs, instead of the one that was updated. This is because they have the exact same property names.
I've got a work-around that was part of an earlier question. Basically it doesn't write the ModelState unless the object is invalid. For the invalid scenario I don't show the address list which basically hides the problem. Otherwise every edit button would show the same form content.
What I want to know is if there is a better UX approach to allow a user to edit one of the child Addresses in the list?
My goals are to find a better approach using ASP.NET MVC that:
Follow the PRG (Post-Redirect-Get) pattern
Don't redirect to a separate Page/View for editing, which forces the user to navigate back to the contact edit form again
Avoid popups because of the blockers
The solution allows the use Model Binding and Validation for the Address object
What you want is :
1 Form for the contact
Another form for each address
in your controller you will have an action that expects a contact as a parameter (without addresses) and another action that expects an address.
boilerplate code:
[HttpPost]
public RedirectToRouteResult EditAddress(int id, ContactAddressBindingModel address) {
// ...
}
[HttpPost]
public RedirectToRouteResult EditContact(int id, ContactBindingModel contact)
{
// ...
}
public class ContactViewModel : ContactBindingModel
{
public IReadOnlyList<IAddress> Addresses { // ...}
}
public class ContactBindingModel
{
public int? Id { get; set; }
public string Name { get; set; }
}
public class ContactAddressBindingModel : IAddress
{
public int? Id { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
and in the view :
<form action="EditContact">
<!-- contacts inputs etc -->
</form>
// in razor you can do EditorFor(m => m.Addresses) instead of foreach
// and have partialview, or whatever you like.
#foreach (Model.Addresses) {
<form action="EditAddress">
<!-- address inputs etc -->
</form>
}

NHibernate + ASP.Net MVC + User Activity Feed

I am looking for the most appropriate way of dealing with a user activity feed on my social networking site. At the moment i have several activities which can appear on the news feed such as:
Users joins the site
User comments on a post
User adds a post to their favorites
User adds a new post to the site
Here is a simplified version of my domain objects at the moment:
public abstract class NewsItem : Entity, ITenantSpecific
{
public virtual Account Account { get; set; }
public virtual DateTime DateTime { get; set; }
// returns formatted news html string which gets
// overridden by inherted classes
public abstract string GetNewsHtml();
}
public class NewsItemJoiner : NewsItem
{
public virtual Account AccountJoined { get; set; }
public override string GetNewsHtml()
{
return "XXX has just joined our music network";
}
}
As you can see at the moment I have a property which must be overridden on each activity called GetNewsHtml. This isn't ideal as I don't believe my domain should be responsible for generating my HTML.
I have thought about using a partial view for each activity type and pass into it the NewsItem base class downcasted into the correct type.
However I am open to suggestions.
I have a similar issue but with different order types. I decided to define rendering at the view layer (web/controllers), not domain. You can do it this way:
public interface IRenderer<T> where T: NewsItem
{
string Render(T item);
}
public class NewsItemJoinerRenderer: IRenderer<NewsItemJoiner>
{
public string Render(T item)
{
return "XXX has just joined our music network";
}
}
public class NewsRendererFactory
{
public IRenderer<T> GetRenderer<T>()
{
return ServiceLocator.GetInstance<IRenderer<T>>();
}
}
Then you can pass NewsRendererFactory to controller. Perhaps there's a way to avoid ServiceLocator but for now I cannot tell.
Notice that this makes your architecture both configurable and pluggable if needed.
You can define additional render-related interfaces, add more properties to the IRenderer - for example, PartialName, etc, or have lambda filters on IRenderer that Factory uses to decide if this interface implementation is applicable for the passed (to GetRenderer("some-condition")) condition. A lot of things are possible.
If you don't want IoC containers (ServiceLocator), you can have its job done with simple switch() statement inside NewsRendererFactory.GetRenderer. This will isolate the logic inside single factory method, and you'll be able to replace it easily with true IoC once you are ready.
Update: how to get renderers.
If you don't use IoC, you do something like
typeof(IRenderer<>).Assembly.GetTypes().Where(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IRenderer<>) &&
x.GetGenericArguments().FirstOrDefault() == requestedTypeArguments)
Then you can select SingleOrDefault() or ToList() if you can handle multiple renderers.

Categories

Resources