MVC 5 View from base controller (controller inheritance) - asp.net-mvc-5

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
};
}

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 ActionLink with Areas

http://localhost:55201/SERVICES/customers/CALLME
how to make this link using #Html.ActionLink
Services is RouteArea, Customers is RoutePrefix and Customer is Controller
#Html.ActionLink("Click me", "CallMe",new { area = "Services"})
is this code right? its returning blank anchor.
[RouteArea("Services")]
[RoutePrefix("Customers")]
public class CustomerController : Controller
{
[Route("~/Test")]
public ActionResult Index()
{
ViewBag.Controller = "Customer";
ViewBag.Action = "Index";
return View("ActionName");
}
[Route("CallMe")]
public string CallMeFunction()
{
return string.Format("CallMeFunction() invoked");
}
}

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 Dynamic route on the root

I want the following to urls to be mapped dinamically to the HomeController => Index with the segment variable...
www.site.com/one
www.site.com/two
public HomeController
{
[Route("{segment:string}")]//this wont work... 404
public ActionResult Index(string segment) //one or two
{
return View();
}
}
I think I managed to do it like this:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
// other routes
}
}
[RoutePrefix("")]
public class HomeController : Controller
{
[Route("{segment}")]
public ActionResult test(string segment)
{
return View();
}
}
and accessed the following urls:
http://localhost/TestWebApplication/segment1 -> goes to test action -> put segment = "segment1"
So, shortly put: make sure that attribute routing is enabled and skip the string constraint (I receive an error that is not recognized).

Orchard throws 'The model item passed into the dictionary is of type Part but this dictionary requires a model of type VM

This is an editor for WebShop Global Settings. I needed to extend the editor with a ViewModel. It worked fine before I started but now crashes with the above error when it's invoked. What am I doing wrong?
Here's the driver:
public class WebShopSettingsPartDriver : ContentPartDriver<WebShopSettingsPart>
{
private readonly ISiteService _siteService;
private readonly IWebshopSettingsService _webshopSettings;
protected override string Prefix { get { return "WebShopSettings"; } }
private const string shapeName = "Parts_WebShopSettings_Edit";
private const string templateName = "Parts/WebShopSettings";
public WebShopSettingsPartDriver(IWebshopSettingsService webshopSettings, ISiteService siteService)
{
_webshopSettings = webshopSettings;
_siteService = siteService;
}
protected override DriverResult Editor(WebShopSettingsPart part, dynamic shapeHelper)
{
var settings = _siteService.GetSiteSettings().As<WebShopSettingsPart>();
var model = new WebShopSettingsVM
{
WebShopSettings = settings,
ShippingProducts = _webshopSettings.ShippingProductRecords()
};
return ContentShape(shapeName,
() => shapeHelper.EditorTemplate(TemplateName: templateName, Model: model, Prefix: Prefix)).OnGroup("WebShop");
}
}
}
Here is the Handler:
public class WebShopSettingsPartHandler : ContentHandler {
public WebShopSettingsPartHandler(IRepository<WebShopSettingsRecord> repository) {
T = NullLocalizer.Instance;
Filters.Add(new ActivatingFilter<WebShopSettingsPart>("Site"));
Filters.Add(StorageFilter.For(repository));
OnGetContentItemMetadata<WebShopSettingsPart>((context, part) => context.Metadata.EditorGroupInfo.Add(new GroupInfo("WebShop")));
}
}
And here is the first line of the View (which is in Views\EditorTemplates\Parts\WebShopSettings.cshtml):
#model Cascade.WebShop.ViewModels.WebShopSettingsVM
The Placement.ini file has the following entry:
<Place Parts_WebShopSettings_Edit="Content:0" />
Here is the ViewModel:
public class WebShopSettingsVM
{
public IEnumerable<ShippingProductRecord> ShippingProducts{ get; set; }
[Required]
public int? ShippingProductRecordId { get; set; }
public WebShopSettingsPart WebShopSettings { get; set; }
// Expose all the properties of the Part directly on the VM
[Required]
public string AdministratorEmailAddress
{
get { return WebShopSettings.AdministratorEmailAddress; }
set { WebShopSettings.AdministratorEmailAddress = value; }
}
[Required]
public string ContinueShoppingUrl
{
get { return WebShopSettings.ContinueShoppingUrl; }
set { WebShopSettings.ContinueShoppingUrl = value; }
}
// and so on...
}
After Bertrand's suggestion below I updated the View to:
#using Cascade.WebShop.ViewModels
#using Cascade.WebShop.Models
#{
var vm = Model.Model as WebShopSettingsVM;
}
<fieldset>
<legend>#T("Webshop")</legend>
<div>
<label for="#Html.FieldIdFor(x=>vm.AdministratorEmailAddress)">#T("Administrator email address")</label>
#Html.TextBoxFor(x=>vm.AdministratorEmailAddress, new { #class = "textMedium" })
#Html.ValidationMessage("AdministratorEmailAddress", "*")
...
Insights and suggestions greatly appreciated -- I simply can't see what's wrong.
A second copy of the driver, under a slightly different name, was present in the 'Helpers' directory. Not surprisingly, I didn't notice this. The 'Helper' driver was supplying a Part and the 'proper' driver a VM. Both were being fired and thus whether I used a VM or a Part one or other of the two drivers would throw an exception.
Deleting the spurious driver fixed the problem. Sorry Bertrand.
The model is still the shape. Remove the directive, and access Model.Model to access your view model: Model is the shape, and Model.Model is the property named Model that is on the shape.
Just cast Model.Model to the view model's type.

Resources