RazorPages: Model does not get instantiated in Partial with Page Model - asp.net-core-2.0

Im testing out RazorPages and .Net Core 2.1
I have just taken a new project template and have created a Partial.
These are relevant/added contents of the files.
My problem is
1) Immediate problem: In the partial: OnGetAsync (nor public void OnGet()) does not get called. and I get a NullReference-exceptiion in View on Model on line
#foreach (var item in Model.ImageBE) {
I have tried to cut out DB-call and excplicitly call OnGet from contructor but no difference.
2) I cant to find an example where the Page(index) has an instance of the Partials model (ImageGalleryModel below). but this is the only thing the compiler will accept. Am I doing this totally wrong?
Index.cshtml (the page)
...
[partial name="_ImageGallery" model="Model.ImageGallery" /]
...
Index.cshtml.cs
public class IndexModel : PageModel
{
ApplicationDbContext mContext;
public ImageGalleryModel ImageGallery;
public IndexModel(ApplicationDbContext context)
{
mContext = context;
ImageGallery = new ImageGalleryModel(mContext);
}
public void OnGet()
{
}
}
_ImageGallery.cshtml (the partial)
[table class="table"]
#foreach (var item in Model.ImageBE) {
...
_ImageGallery.cshtml.cs
public class ImageGalleryModel : PageModel
{
private readonly ApplicationDbContext _context;
public IList<ImageBE> ImageBE { get; set; }
public ImageGalleryModel(Photiqo.Data.ApplicationDbContext context)
{
_context = context;
}
public async Task OnGetAsync()
{
ImageBE = await _context.ImageBE.ToListAsync();
}
}

Partials should not have a PageModel file associated with them. If you have C# code that you want to execute, you should consider creating a ViewComponent.
Alternatively, you can move the public IList<ImageBE> ImageBE property to the IndexModel and instantiate it in the OnGetAsync method there. Then you can specify the model type on the partial and pass it to the partial using the tag helper as you currently re doing:
_ImageGallery.cshtml (the partial)
#model IList<ImageBE>
<table class="table">
#foreach (var item in Model) {
...

Related

How to inherits a BaseRazorPage in Razor page

In razor engine way, I can define a BaseRazorPage for all razor views
public abstract class BaseRazorPage<TModel> : RazorPage<TModel>
{
protected BaseRazorPage()
{
}
protected virtual string L(string name)
{
return XXX.Localization.L.Text[name];
}
......
}
Use it in _viewImports.cshtml
#inherits BaseRazorPage<TModel>
Then I can use the L function to do mutiple language in views:
#L("Hello word!")
How can I implement same function in Razor page way? Or is there an alternative way to do this?
The razor page can't inherits any class.
A simple solution would be to create an extension for the PageModel class.
public static class PageModelExtensions
{
public static string L(this PageModel pageModel, string name)
{
// return a new value. put your logic here
return name + "_result";
}
}
Now we can use the L method as a member function.
public class IndexModel : PageModel
{
public void OnGet()
{
string value = this.L("test");
}
}
Or we can use the L method in the view like this
#page
#model IndexModel
#{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>#Model.L("test")</p>
</div>
I hope this helps.
UPDATE
If you want to have a base class with your common methods the following example is what you want.
public class MyPageModel : PageModel
{
public string L(string name)
{
return "sample";
}
}
And your razor page class will look like this.
public class IndexModel : MyPageModel
{
public void OnGet()
{
string value = this.L("test");
}
}
In case you want to inject an object in your base class then you base class should look like this.
public class MyPageModel : PageModel
{
private readonly ApplicationDbContext context;
public MyPageModel(ApplicationDbContext context)
{
this.context = context;
}
public string L(string name)
{
return "sample";
}
}
And the Razor page will look like this
public class IndexModel : MyPageModel
{
private readonly ApplicationDbContext context;
public IndexModel(ApplicationDbContext context)
: base(context)
{
this.context = context;
}
public void OnGet()
{
string value = this.L("test");
}
}

MVC 5 View from base controller (controller inheritance)

does anyone know how tell the child controller to load base (parent) controller view instead of looking for it in it's own folder?
public class BaseController : Controller
{
public virtual ActionResult Test()
{
return View("Test");
}
}
public class ChildController:BaseController
{
public override ActionResult Test()
{
return base.Test();
}
And the Error is The view 'Test' was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Child/Test.cshtml.....
So the solution that I found yeat is to make parent action looks like this
public class BaseController : Controller
{
public virtual ActionResult Test()
{
return View("~Views/Base/Test.cshtml");
}
}
Is there any better solution ?
private const string ViewPath = "~/Views/{0}/{1}.cshtml";
private ViewResult GetView(string action, object model = null)
{
var controllerName = ControllerContext.RouteData.Values["controller"].ToString();
var path = string.Format(ViewPath, controllerName, action);
return
new ViewResult
{
ViewName = path
};
}

Add custom table record

I have been trying to add custom DAC record which is the in database. But it is now working. Here is how I have tried to accomplish.
public class SquarePOSTransactionInquiry : PXGraph<SquarePOSTransactionInquiry>
{
public PXSave<MasterTable> Save;
public PXCancel<MasterTable> Cancel;
public PXFilter<MasterTable> MasterView;
public PXSelect<INSquarePOSTransaction> INSquarePOSTransactions;
public PXAction<MasterTable> calc;
[PXUIField(DisplayName = "Sync Square Transactions")]
[PXProcessButton()]
protected virtual IEnumerable Calc(PXAdapter adapter)
{
PXLongOperation.StartOperation(this, () =>
{
using (var scope = new PXTransactionScope())
{
INSquarePOSTransaction trans = new INSquarePOSTransaction();
trans.TransacationCD = "new";
trans.Description = "Another new";
var test = this.INSquarePOSTransactions.Insert(trans);
this.INSquarePOSTransactions.Cache.IsDirty = true;
//this.INSquarePOSTransactions.Update(trans);
this.Actions.PressSave();
scope.Complete();
}
});
return adapter.Get();
}
public SquarePOSTransactionInquiry()
{
}
[Serializable]
public class MasterTable : IBqlTable
{
}
}
I tried setting cache IsDirty property to false, but that didn't help too. But the strange part is updating the DAC is working. I have even looked into other Business Logic codes from other pages and it looks same like I have tried above. Could you please tell me what I am missing?
Thanks.
Within the method that you pass to StartOperation(),
you have to create a new instance of the graph and invoke the processing method on that instance.

Use a part from a different module in Orchard

What I'm trying to do is create a site in Orchard that doesn't have a way for a user to register. An administrator will create the users.
What I have is module that defines the parts, records, views, etc. That is basically working.
Now what I'm trying to do is add a UserPart (from Orchard.Users) to one of the parts in my module.
I'm not sure how to do that. I need the fields displayed for the UserPart with the fields for the parent part in the same view. This also needs to be done in a way that when a save happens, all of the UserPart fields get sent to the Orchard.Users module.
Any suggestions, pointers or links on how to do that?
Thanks!
UPDATE...
The Activating Filter is an interesting idea. I initially chose the migration route. For now, I'll try and get that method working.
For simplicity, let's say I have a "Company" type (there's more to the actual type) that has a "CompanyName" and a UserPart.
Here's what the different pieces look like...
Migrations.cs (simplified)
public int Create()
{
SchemaBuilder.CreateTable("CompanyPartRecord", table => table.ContentPartRecord()
.Column("CompanyName", DbType.AnsiString, c => c.WithLength(50))
.Column("UserId", DbType.Int32));
SchemaBuilder.CreateForeignKey("FK_CompanyPartRecord_UserPartRecord", "CompanyPartRecord", new[] {"UserId" }, "Orchard.Users", "UserPartRecord", new[] { "Id" })
ContentDefinitionManager.AlterTypeDefinition("Company", type => type.WithPart("CommonPart").WithPart("UserPart"));
}
CompanyPartRecord
public class CompanyPartRecord : ContentPartRecord
{
public virtual string CompanyName { get; set; }
public virtual int? UserId { get; set; }
}
CompanyPart
public class CompanyPart : ContentPart<CompanyPartRecord>
{
internal LazyField<UserPart> UserPartField = new LazyField<UserPart>();
public string CompanyName
{
get { return Record.CompanyName; }
set { Record.CompanyName = value; }
}
public UserPart User
{
get { return UserPartField.Value;}
set { UserPartField.Value = value; }
}
}
Handler
public class CompanyPartHandler : ContentHandler
{
private readonly IContentManager _manager;
public CompanyPartHandler(IRepository<CompanyPartRecord> repository, IContentManager manager)
{
_manager = manager;
Filters.Add(StorageFilter.For(repository));
OnActivated<CompanyPart>(OnActivatedHandler);
}
private void OnActivatedHandler(ActivatedContentContext context, CompanyPart part)
{
if(part.User == null)
{
part.User = _manager.Create<UserPart>("User");
}
else
{
part.User = _manager.Get<UserPart>(part.User.Id);
}
}
}
Driver
public class CompanyPartDriver : ContentPartDriver<CompanyPart>
{
protected override DriverResult Editor(CompanyPart part, dynamic shapeHelper)
{
return ContentShape("Parts_Company_Edit", () => shapeHelper.EditorTemplate(TemplateName: "Parts/Company",
Model: part, Prefix: Prefix));
}
protected override DriverResult Editor(CompanyPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
Controller
public class AdminCompanyController : Controller, IUpdateModel
{
private readonly IOrchardServices _services;
private readonly INotifier _notifier;
private readonly IContentManager _contentManager;
private readonly ITransactionManager _transactionManager;
private readonly Localizer T = NullLocalizer.Instance;
public AdminCompanyController(IOrchardServices services)
{
_services = services;
_notifier = services.Notifier;
_contentManager = services.ContentManager;
_transactionManager = services.TransactionManager;
}
public ActionResult Create()
{
var company = _contentManager.New<CompanyPart>("Company");
var model = _contentManager.BuildEditor(company);
return View(model);
}
[HttpPost, ActionName("Create")]
public ActionResult CreatePOST()
{
var contentItem = _contentManager.New<CompanyPart>("Company");
var model = _contentManager.UpdateEditor(contentItem, this);
if (!ModelState.IsValid)
{
_transactionManager.Cancel();
return View(model);
}
_contentManager.Create(contentItem.ContentItem);
_notifier.Information(T("Company has been saved"));
return RedirectToAction("Index");
}
public ActionResult Edit(int Id)
{
var contentItem = _services.ContentManager.Get(Id);
dynamic model = _services.ContentManager.BuildEditor(contentItem);
return View(model);
}
[HttpPost, ActionName("Edit")]
public ActionResult EditPOST(int Id)
{
var contentItem = _contentManager.Get<CompanyPart>(Id);
var model = _contentManager.UpdateEditor(contentItem, this);
_notifier.Information(T("Company has been saved"));
return RedirectToAction("Index");
}
public ActionResult Delete(int Id)
{
var contentItem = _contentManager.Get<CompanyPart>(Id);
_contentManager.Destroy(contentItem.ContentItem);
return RedirectToAction("Index");
}
bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties)
{
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
}
public void AddModelError(string key, LocalizedString errorMessage)
{
ModelState.AddModelError(key, errorMessage.ToString());
}
}
View (create)
#{ Layout.Title = T("Add Company").ToString(); }
#using (Html.BeginFormAntiForgeryPost())
{
#Display(Model)
}
Editor Template
#model SDS.Models.CompanyPart
<fieldset>
#Html.LabelFor(m => m.CompanyName)
#Html.TextBoxFor(m => m.CompanyName)
</fieldset>
#*
What goes here to display UserPart?
*#
So here's where I'm at. I can see the ContentItem (CompanyType). I can put in the name and save it. The name is getting saved to the db. Right now the UserPart is getting saved to the db, but all of the fields are blank.
The part I'm stuck on is what to put in the editor template to display the UserPart fields so that the values get to the UserPart driver and ultimately the db.
Any ideas on how to do that?
Thanks!
So you don't attach parts to parts, you attach parts to content items, and you can do that in multiple ways.
You can do it through the admin screen, but that isn't a code driven solution and would have problems if you have multiple environments or need to redeploy a fresh version of code.
You can attach the part when you create a new content item in the migration. This might be a good solution, if you already ran your migration you could possibly do it with an update migration. This allows the part to be managed through the admin screen, but has downsides because it can be removed and if you have code that relies on the part then you will start having errors.
The last way and best way is to attach the part dynamically using an Activating Filter.
ActivatingFilter class - Attaches a part to a content type from code. As opposed to attaching parts via migrations, parts attached using this filter will neither be displayed in the Dashboard, nor users will be able to remove them from types. It's a legitimate way of attaching parts that should always exist on a given content type.
So to do this:
1. Add a reference to Orchard.Users to your custom project.
2. Create a handler for you part. Such as MyPartHandler
3. Then add the activating handler like so
Filters.Add(ActivatingFilter.For<UserPart>("MyContentType"));
So now anywhere in your code you can access the UserPart if you already have your part, or the content item using
var userPart = myPart.As<UserPart>();

MVC5 - Entity loaded in base controller conflicting with entity updates elsewhere

I have a base controller that loads an EF6 entity in a protected Dictionary member. But somehow it stops other controllers from updating that entity. Below is the simplified code -
public abstract class BaseController : Controller {
protected IDictionary<string, int> MyList;
public BaseController() {
MyList = new Dictionary<string, int>();
foreach (var rc in db.MyTable.Where(r => r.IsActive).ToList())
MyList.Add(rc.Name, rc.Id);
ViewBag.MyListViewBag = MyList;
}
}
public class MyController : BaseController {
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,Name,...other properties")] MyTable mt) {
if (ModelState.IsValid)
{
db.Entry(mt).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(mt);
}
}
The error is "Attaching an entity of type failed because another entity of the same type already has the same primary key value" and it is thrown at db.Entry(mt).State = EntityState.Modified;
As you can see, BaseController loads the entity in a list first, that should severe any connections (at least I thought it would). Is there a way around it?
Your mt variable, which you take from action arguments, is not a part of EF tracked objects. So, you should take it from db and then update it's necessary properties:
public ActionResult Edit([Bind(Include = "Id,Name,...other properties")] MyTable mt)
{
if (ModelState.IsValid)
{
var temp = db.MyTable.Single(mt.Id);
temp.Name = mt.Name;
//copying of other properties...
db.SaveChanges();
return RedirectToAction("Index");
}
return View(mt);
}

Resources