I have created a view component.
public class PreviewCVComponent : ViewComponent
{
..
public async Task<IViewComponentResult> InvokeAsync(int id)
{
return View();
}
}
I have added a folder into views/shared/components which is given the name PreviewCV. Under that folder I have added a view called Default.cshtml.
The Component is called from another view. Which is located under views/CV, and has the name CV.cshtml.
I am trying to call the component with the use of
#await Component.InvokeAsync("PreviewCV", new { id = -1 })
This results in:
InvalidOperationException: A view component named 'PreviewCV" could not be found. A view component must be a public non-abstract class, not contain any generic parameters, and either be decorated with 'ViewComponentAttribute' or have a class name ending with the 'ViewComponent' suffix. A view component must not be decorated with 'NonViewComponentAttribute'.
I am using .net core.
--In Models folder
FileName: TestViewComponent.cs
--In TestViewComponent class
[ViewComponent(Name = "TestViewComponent")] //Solution
public class TestViewComponent: ViewComponent
{
public IViewComponentResult Invoke()
{
return View();
}
}
Put ViewComponent in suffix solve the problem.
As suggested by Kirk Larkin. I need to include the View in the class.
Using net core 5.0, class name (e.g. PreviewCV) can either be written as PreviewCV OR PreviewCVViewComponent
so
public class PreviewCV : ViewComponent
and
public class PreviewCVViewComponent : ViewComponent
will work fine while below will generate error (same as shown in question)
public class PreviewCVComponent : ViewComponent
Also noticed that attribute didn't have any effect on these
[ViewComponentAttribute]
One solution is to call it like:
#await Component.InvokeAsync(typeof(PreviewCVComponent), new { id = -1 })
Related
I have a static variable inside of my main project (mvc), I am wanting to be able to pass/use that variable in my other project (asp.net core 5.0 web api) project. I was reading up on how you can perform this task, one of the ways is using a static variable which I have. I read this post and one of the solutions mentions you can call that static variable from the first project into the other project by calling the namespace of that first project in the first project. However, when I do so it does not let me it says it does not exist. Is there a way to be able to do this?
On the post their example was:
using System.Windows.Forms;
namespace Demo1
{
public class Sample1
{
public static string x = "initial value of 'x";
public void sampleFn1() {x = "value of 'x set in function";}
}
}
namespace Demo2
{
public class Sample2
{
public void sampleFn2(){MessageBox.Show(Demo1.Sample1.x);}
}
}
For me, Project 1 is CustomerApp and Project 2 is Service.Api:
namespace CustomerApp.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public static Guid uniqueId = Guid.NewGuid();
}
}
Then in my ServiceApi I tried performing the same as the example from the post, but when I call the namespace CustomerApp it does not give me any options to reference it to the other project. Is there a specific using I need to use in order to replicate the example from the post?
namespace Service.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly ILogger<ValuesController> _logger;
public ValuesController(ILogger<ValuesController> logger)
{
_logger = logger;
}
// GET: api/<ValuesController>
[HttpGet]
[Route("sample")]
public ActionResult Get()
{
_logger.LogInformation("Target method invoked.....{#CorrelationId}", CustomerApp.); // here I am trying to perform CustomerApp.Controllers.HomeController.uniqueId.ToString()
}
}
}
You will want to make sure that the static variable is defined in a Class Library project, and that the Web project has a reference to the Class Library project.
The way your code is currently constructed, it looks like both projects are web projects (your first project mentions the HomeController). As much as possible, avoid this type of co-mingling. Move business logic into the class library, and keep the web logic in the web project.
I think you forgot to add the project reference to destination project where you want to use the variable ,
Add reference like this :
right-click on your destination project and select Add > Project Reference
and in final choose the variable project
this will help you
first, if you haven't, add it to the HomeController class (using CustomerApp.Controllers.HomeController;). Your own example does not match the example you reference. In the first example, a static variable is used between two different classes in the same namespaces, but in your example you are trying to operate between different namespaces and different classes.
So if your service is inside your main project you should add.
then you can use it as below.
public ActionResult Get()
{
_logger.LogInformation("Target method invoked.....{#CorrelationId}", HomeController.uniqueId.ToString());
}
For Asp.net mvc core app:
In one of my dbcontext tables I have records with HelpInfo (id as primary key, and string for helpInfo) that I want to use as html title attributes in my razor views, such as:
<div title='#I(12)'></div>
Where 12 is an example id of a HelpInfo record and #I should be a global accessible class method that fetches the corresponding helpInfo string.
I tried to implement King Kings approach such as:
The CustomView:
public abstract class CustomView<TModel> : RazorPage<TModel>
{
protected IInfo Info => Context.RequestServices.GetRequiredService<IInfo>();
public Task<string> I(int code)
{
return Info.GetAsync(code);
}
}
The Interface:
public interface IInfo
{
Task<string> GetAsync(int code);
}
The Service:
public class Info : IInfo
{
private readonly ApplicationDbContext context;
public Info(ApplicationDbContext context)
{
this.context = context;
}
public async Task<string> GetAsync(int code)
{
var myRecord = await context.MyRecords.FindAsync(code);
if (myRecord != null)
{
return myRecord.Info;
}
return null;
}
}
In _ViewImports.cshtml I added #inherits CustomView<TModel>
In the view I used <div title='#(await I(12))'>Test</div>
When I load the view I get
No service for type '...Models.IInfo' has been registered
Any help to pin down the problem would be appreciated.
As I understand looks like you want something like #Html or #Url which are supported in the default Page and RazorPage. That's just a custom property exposed by the base page. So in your case, you need a custom view (for mvc) or a custom page (for razor pages). Here is an example of a custom view used for MVC:
public abstract class CustomView<TModel> : RazorPage<TModel>
{
//custom members can be declared in here
}
Based on your desired usage of I, it must be a method. So it can be a method exposed by some service injected in your base page. Suppose that interface is like this:
public interface IInfo {
string Get(int code);
}
Now your custom page can implement the I method like this:
public abstract class CustomView<TModel> : RazorPage<TModel>
{
//NOTE: the custom page class does not support constructor injection
//So we need to get the injected services via the Context like this.
protected IInfo Info => Context.RequestServices.GetRequiredService<IInfo>();
public string I(int code){
return Info.Get(code);
}
}
To use your custom view, you need to use the directive #inherits in your view or better in the _ViewImports.cshtml so that you don't have to repeat that #inherits everywhere, like this:
#inherits CustomView<TModel>
Sometimes the base view is not applied (so the base members are not available) until you rebuild your project.
Now you can use the I in your views as what you desire, like this:
<div title="#I(12)"></div>
Note the I method returns a string in my example, you can also make it return an IHtmlContent (e.g: HtmlString) or whatever type you need for your requirement.
NOTE:
Technically you can use any services (including ones querying for data from database), but in such cases please ensure that the querying is as fast as possible and use async to have the best performance & avoid thread starvation. Here is an example of the IInfo service that queries from a database:
public interface IInfo {
Task<string> GetAsync(int code);
}
public class Info : IInfo {
//inject whatever your Info needs
//here we just need some DbContext (used in EFCore)
public Info(InfoDbContext dbContext){
_infoDbContext = dbContext;
}
readonly InfoDbContext _infoDbContext;
public async Task<string> GetAsync(int code){
var info = await _infoDbContext....
return info;
}
}
The I method then should be async as well:
public Task<string> I(int code){
return Info.GetAsync(code);
}
I hope that you know how to register a DbContext to use in your code (that's part of EFCore so you may have to learn more about that first). Now to use the async method, you call it in your view like this:
<div title="#(await I(12))"></div>
Again, I would try to avoid querying the db in such a helper like that. As I said, the helper's method can be called multiple times right in one same view so usually we have fast methods or use caching for the info it need. This is related to another feature called caching which has more to be put in one short answer, you can learn more about that.
Solution:
To my Startup.cs I added:
services.AddScoped<IInfo, Info>();
services.AddTransient<IInfo, Info>(); also does work.
A new MVC 5 app that I'm working on references a large-ish collection (4 assemblies, about 500 classes each) of data models generated from a 4GL environment. The basic interaction has the MVC app present and populate a model instance, then (after model validation), hand the model instance off to a provider for processing.
The initial approach I've used is, for each model, create
a scaffolded razor view bound to the model,
a partial controller class with a pair of actions (GET/POST) the model
All of the actions are part of the same controller class which has a couple of private methods to implement the GET & POST actions exposed in each of the partials.
So, the structure is like:
|
|--\Controllers
|
|--MyController.cs
|--MyController.MDL001.cs
|--MyController.MDL002.cs
|-- ...
|--MyController.MDL500.cs
|--\Views
|
|--\My
|--\MDL001.cshtml
|--\MDL002.cshtml
|-- ...
|--\MDL500.cshtml
And the implementation of each partial controller follows the pattern:
public partial class MyController
{
public ActionResult ProcessMDL001(MDL001Model modelInstance)
{
return ProcessModel(modelInstance);
}
public ActionResult MDL001()
{
return ShowModel("MDL001");
}
}
Where methods ProcessModel(...) and ShowModel(...) are defined in MyController.cs
I want to keep MVC's model binding and validation functioning but also am keen on avoiding a few thousand nearly-identical concrete action implementations. Is there some pattern/approach using routing, generics, dynamics, etc. that can help here?
Assuming that you can roughly treat each class the same, you can handle this with generics:
public class BaseController<T> : Controller
where T : class, new
{
public ActionResult Process(T modelInstance)
{
return ProcessModel(modelInstance);
}
...
}
But, you would need to use subclasses instead of partial classes. Essentially, you're just going to implement the action once, and then subclass this base controller to specify the type for the controller instance you're working with:
public MDL001ModelController : BaseController<MDL001Model>
{
}
If no additional type-specific actions are needed, then that code alone is all you subclass would need to be. However, you can always add additional actions that will only apply to this particular controller instance.
If there are pieces of the common actions that you need to customize slightly, such as validation logic or something, you can provide hooks in your actions. Something along the lines of:
public class BaseController<T> : Controller
where T : class, new
{
public ActionResult ActionWithHook(T model)
{
DoSomeWork(model);
return View();
}
// By default this does nothing, but can be overridden to do something
internal virtual void DoSomeWork(T model)
{
}
}
Then:
public MDL001ModelController : BaseController<MDL001Model>
{
internal override void DoSomeWork(MDL001Model model)
{
// Do something
}
}
I've converted my project from .Net framework 4 to 4.5.
I have done this to make use of the Nuget package MvcMailer.
All is good except in the UserMailer class the following code exists:
public virtual MvcMailMessage Welcome()
{
//ViewBag.Data = someObject;
return Populate(x =>
{
x.Subject = "Welcome";
x.ViewName = "Welcome";
x.To.Add("some-email#example.com");
});
}
The Populate word throws an error:
The name 'Populate' does not exist in the current context
To what Namespace does the word Populate belong to?
Or is it an extension?
I can't find anything on the net.
It's a class method of MailerBase controller with this signature (from source code on GitHub):
public virtual MvcMailMessage Populate(Action<MvcMailMessage> action)
To use it you must derive your controller from MailerBase (it's the base class for Mailers. Your mailer should subclass MailerBase).
For example, supposing your controller is named Home, from:
public class Home : Controller {
To:
public class Home : MailerBase {
It's in Mvc.Mailer namespace (same of MvcMailerMessage class) anyway it's not an extension method so you don't even need to worry about it.
Put the cursor on the keyword and hit Alt+Shift+F10. It will show you it's source, and you'll be able to include the whole namespace, or use the keyword just once. It will only work if you have a correct .dll reference in your project.
I think you got the code from this GitHub repository.
https://github.com/smsohan/MvcMailer/wiki/MvcMailer-Step-by-Step-Guide
public virtual MvcMailMessage Welcome()
{
ViewBag.Name = "Sohan";
return Populate(x =>{
x.viewName = "Welcome";
x.To.Add("sohan39#example.com");
});
}
This code was provided by the Developer of the Package and he showed how to use it to edit the MvcMailer.
If so, the guy there used these namespaces in the top of his C# file.
using Mvc.Mailer;
So, I guess it would be a part of this Namespace. Include it to your project and you're done!
So, I'm trying to find the Umbraco node (as iPublishedContent), and pass it to the viewModel (as ะจ've hijacked a route). So i put this in my controller:
private AddCouponCodesViewModel viewModel;
public AddCouponCodesController(){
//Get iPublished content
IPublishedContent content = Umbraco.TypedContent(1225);
//Pass to viewModel
viewModel = new AddCouponCodesViewModel(content);
RouteData.DataTokens["umbraco"] = content;
}
public ActionResult Index()
{
//return view etc
}
But I'm getting
Exception Details: System.NullReferenceException:
Object reference not set to an instance of an object.
here:
Source Error(AddCouponCodesViewModel.cs):
Line 20:
Line 21: }
Line 22: public AddCouponCodesViewModel(IPublishedContent content)
Line 23: : base(content)
Line 24: {
AddCouponCodeRenderModel.cs:
public class AddCouponCodesViewModel : RenderModel
{
public string test { get; set; }
public List<string> tables { get; set; }
public List<string> errors { get; set; }
public AddCouponCodesViewModel(IPublishedContent content, CultureInfo culture) : base(content, culture)
{
}
public AddCouponCodesViewModel(IPublishedContent content)
: base(content)
{
}
And this is the Global.asax
public class Global : UmbracoApplication
{
protected override void OnApplicationStarted(object sender, EventArgs e)
{
base.OnApplicationStarted(sender, e);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//AreaRegistration.RegisterAllAreas();
//WebApiConfig.Register(GlobalConfiguration.Configuration);
//FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
//RouteConfig.RegisterRoutes(RouteTable.Routes);
base.OnApplicationStarting(sender, e);
RouteTable.Routes.MapRoute(
"AddCouponCodes", // Route name
"Admin/{controller}/{action}/{id}", // URL with parameters
new { controller = "AddCouponCodes", action = "Index", id = "" } // Parameter defaults
);
}
}
The content is published (I've checked and double checked), and the node ID is correct.
What I'm basically trying to do here, is to get the route example.com/Admin/{controller}/{action}/{parameter}
To be routed, but having problems connecting it with the umbracoNode (And class RenderModel requires a iPublishContent object as a parameter, but I'm in no luck when trying to pass it anything)
Could someone please help me here, been stuck way too many hours on this :-(
To clarify, if you are hijacking a route, it means that you are overriding the way Umbraco passes it's RenderModel to one of it's published pages. You can either do this globally by overriding the main RenderMvcController, or you can override on a DocumentType-specific basis. So for example, if I have a Homepage doc type, I could create:
public HomepageController : RenderMvcController
{
public override ActionResult Index(RenderModel model)
{
// Create your new renderModel here, inheriting
// from RenderModel
return CurrentTemplate(renderModel);
}
}
This would route all calls to the homepage through this one action. For this, you don't need to define any new routes in the route table. And you should override the render model in the action not in the constructor.
Your question is slightly confusing and it's not entirely clear what you are trying to achieve because:
You have defined a route, and
In your constructor you are calling Umbraco.TypedContent(1225) to retrieve a specific published node
So ... if the admin page you are trying to route has itself been published by Umbraco (and it doesn't sound like it has), the just create a new controller with the name of the page's document type and override the render model in the way described above.
However ... if your admin page hasn't been published by Umbraco and you just want the admin page to access node data, then you have a couple of options:
Create a surface controller, inheriting from SurfaceController. This will give you access to the Umbraco context et al; or
Create a standard controller (preferrably in an Area) and inject the ContentCache using something like Autofac
E.g.:
builder.RegisterControllers(typeof (AdminController).Assembly)
.WithParameter("contentCache", UmbracoContext.Current.ContentCache);
Create a standard controller (preferrably in an Area) and access the node using Umbraco's ContentService API, i.e. new Umbraco.Core.Services.ContentService().GetById(1225)
The difference between the last two approaches is that:
Injecting the ContentCache provides you readonly but very quick access to the published content.
Accessing the ContentService provides you read/write access to the nodes themselves but at the expense of speed as you are querying the database directly.
It depends on what your requirement is.
Either way, it is well worth taking time to read through the documentation for hijacking Umbraco routes, and at least trying to understand what is going on.
Well, I can tell you that your view isn't getting fed anything for the Razor markup because your Index method doesn't feed it anything. That's one problem. I can also tell you, that in your AddCouponCodesViewModel, you'll need an empty constructor, so that the razor syntax can just create an instance, and then populate it to match your submitted object to the view.
Modify your ViewController :
public ActionResult Index()
{
return View(viewModel);
}
Modify your AddCouponCodesViewModel to add an Empty constructor:
public AddCouponCodesViewModel()
{
}
Create a paramaterless constructor on your view model like this:
public AddCouponCodesViewModel():
this(new UmbracoHelper(UmbracoContext.Current).
TypedContent(UmbracoContext.Current.PageId))
{
}
This will get the contexts your other constructors are looking for.
After you've created a class with specific constructors, the compiler stops generating a parameterless one by default. Since you need a parameterless constructor, this is how to get one and still pass in the Umbraco contextual info your viewmodel needs