Is it possible to invoke a View Component from controller and render it to a string? I am really looking for some code example for this. Any help will be much appreciated.
As of beta7 it is now possible to return a ViewComponent directly from a controller. Check the MVC/Razor section of the announcement
The new ViewComponentResult in MVC makes it easy to return the result
of a ViewComponent from an action. This allows you to easily expose
the logic of a ViewComponent as a standalone endpoint.
So now the code for returning the sample view component just needs to be:
public class HomeController : Controller
{
public IActionResult Index()
{
return ViewComponent("My");
}
}
Please refer to example from official ASP.NET article on ViewComponent
In their example, the view component is called directly from the controller as follows:
public IActionResult IndexVC()
{
return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}
You can do that but you have to apply following thing as It is render by DefaultViewComponentHelper.
You have to create instance of this and to create that you need IViewComponentSelector and IViewComponentInvokerFactory.
To do this I have done following thing.
public class HomeController : Controller
{
Microsoft.AspNet.Mvc.DefaultViewComponentHelper helper = null;
Microsoft.AspNet.Mvc.Razor.RazorView razorView = null;
public HomeController(IViewComponentSelector selector,IViewComponentInvokerFactory factory,IRazorPageFactory razorPageFactory,IRazorPageActivator pageActivator,IViewStartProvider viewStartProvider)
{
helper = new DefaultViewComponentHelper(selector, factory);
razorView = new Microsoft.AspNet.Mvc.Razor.RazorView(razorPageFactory, pageActivator, viewStartProvider);
}
public IActionResult Index()
{
ViewContext context = new ViewContext(ActionContext, razorView, ViewData, null);
helper.Contextualize(context);
string st1 = helper.Invoke("My", null).ToString();
return View();
}
}
Here is my sample View Component.
public class MyViewComponent : ViewComponent
{
public MyViewComponent()
{
}
public IViewComponentResult Invoke()
{
return Content("This is test");
}
}
Here's a tag helper that I created to embed components via HTML like syntax. Invoking from a TagHelper like this should closely match invoking from a Controller.
ViewComponent Tag Helper
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace TagHelperSamples.Web
{
[HtmlTargetElement("component")]
public class ComponentTagHelper : TagHelper
{
private DefaultViewComponentHelper _componentHelper;
[HtmlAttributeName("name")]
public string Name { get; set; }
[HtmlAttributeName("params")]
public object Params { get; set; }
[ViewContextAttribute] // inform razor to inject
public ViewContext ViewContext { get; set; }
public ComponentTagHelper(IViewComponentHelper componentHelper)
{
_componentHelper = componentHelper as DefaultViewComponentHelper;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
_componentHelper.Contextualize(ViewContext);
output.Content.AppendHtml(
await _componentHelper.InvokeAsync(Name, Params)
);
}
}
}
Usage
<component name="RecentComments" params="new { take: 5, random: true }"></component>
Code from dotnetstep's answer updated for MVC 6.0.0-beta4 (VS2015 RC):
public class HomeController : Controller
{
Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentHelper helper = null;
public HomeController(IViewComponentDescriptorCollectionProvider descriptorProvider, IViewComponentSelector selector, IViewComponentInvokerFactory invokerFactory)
{
helper = new DefaultViewComponentHelper(descriptorProvider, selector, invokerFactory);
}
public IActionResult Index()
{
ViewContext context = new ViewContext(ActionContext, null, ViewData, null, null);
helper.Contextualize(context);
string st1 = helper.Invoke("My", null).ToString();
return View();
}
}
Based on https://gist.github.com/pauldotknopf/b424e9b8b03d31d67f3cce59f09ab17f
public class HomeController : Controller
{
public async Task<string> RenderViewComponent(string viewComponent, object args)
{
var sp = HttpContext.RequestServices;
var helper = new DefaultViewComponentHelper(
sp.GetRequiredService<IViewComponentDescriptorCollectionProvider>(),
HtmlEncoder.Default,
sp.GetRequiredService<IViewComponentSelector>(),
sp.GetRequiredService<IViewComponentInvokerFactory>(),
sp.GetRequiredService<IViewBufferScope>());
using (var writer = new StringWriter())
{
var context = new ViewContext(ControllerContext, NullView.Instance, ViewData, TempData, writer, new HtmlHelperOptions());
helper.Contextualize(context);
var result = await helper.InvokeAsync(viewComponent, args);
result.WriteTo(writer, HtmlEncoder.Default);
await writer.FlushAsync();
return writer.ToString();
}
}
}
and
public class NullView : IView
{
public static readonly NullView Instance = new();
public string Path => string.Empty;
public Task RenderAsync(ViewContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
return Task.CompletedTask;
}
}
Related
I'm trying to use NavivgateTo in Blazor to pass a file id and name to download a file from my Download controller.
What is the proper setup? I've tried a number of possibilities and I keep seeing an error: Sorry, there is nothing at this address.
Razor Page
public async Task SelectedDisplayDbItemChanged(DisplayDbItemsComboBoxItemDTO item)
{
Data = null;
Data = GetDataTable();
var fileId = await utilities.ExportDataTableToFile((DataTable)Data).ConfigureAwait(false);
//navigationManager.NavigateTo($"api/download/fileId/" + fileId + "/fileName/" + "myfile", true);
//?data1=678&data2=c-sharpcorner
navigationManager.NavigateTo($"api/Download/{fileId}/{"myfile"}", true);
}
Controller:
[HttpPost("Download/{fileId}/{fileName}")]
public async Task<IActionResult> Download(string fileId, string fileName)
{
using (var ms = new MemoryStream())
{
var fullPath = Path.Combine(DownloadPath, fileId);
await using (var stream = new FileStream(fullPath, FileMode.Open))
{
await stream.CopyToAsync(ms);
}
ms.Position = 0;
return File(ms, "application/octet-stream", $"{fileName}.xlsx");
}
}
I've seen a lot of examples from the Razor page to the Razor page, but not from NavigateTo to a controller with passing multiple parameters.
I've tried these responses as well: https://stackoverflow.com/a/71130256/9594249
https://stackoverflow.com/a/71130256/9594249
Not like Asp.net MVC or razor page, in Blazor parameters are passed by [Parameter] tag
#page "/Download/{fileId}/{fileName}"
#code {
[Parameter]
public string? fileId { get; set; }
[Parameter]
public string? fileName { get; set; }
}
please refer : https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-6.0
(Updated)
add to Program.cs or Startup.cs:
builder.Services.AddRazorPages(options => {
options.Conventions.AddPageRoute("/DownloadPage", "Download/{fileId?}/{fileName?}");
}
});
Pages/DownloadPage.cshtml
#page "{fileId?}/{fileName?}"
#model BlazorApp.Pages.DownloadModel
Pages/DownloadPage.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace BlazorApp.Pages;
public class DownloadModel : PageModel
{
private readonly IWebHostEnvironment _env;
public DownloadModel(IWebHostEnvironment env)
{
_env = env;
}
public IActionResult OnGet()
{
// work with RouteData.Values["fileId"] and RouteData.Values["fileName"]
}
}
please refer :
https://learn.microsoft.com/en-us/answers/questions/243420/blazor-server-app-downlaod-files-from-server.html
https://learn.microsoft.com/ko-kr/aspnet/core/razor-pages/razor-pages-conventions?view=aspnetcore-6.0
I have a long name of of entity in my code EmployeTraining which used as entity in OData and with same name for the controller.
Startup.cs
app.UseMvc(routeBuilder=>
{
routeBuilder.Expand().Select().Count().OrderBy().Filter().MaxTop(null);
routeBuilder.MapODataServiceRoute("EmployeTraining", "odata/v1", EdmModelBuilder.GetEdmModelEmploye());
});
EdmModelBuilder.cs
public static IEdmModel GetEdmModelEmployes()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<EmployeTraining>("EmployeTraining");
return builder.GetEdmModel();
}
EmployeTrainingControllers.cs
public class EmployeTrainingController : ODataController
{
internal IEmployeService ServiceEmploye { get; set; }
public EmployesController(IEmployeService serviceEmploye)
{
ServiceEmploye = serviceEmploye;
}
//// GET api/employes
[HttpGet]
[MyCustomQueryable()]
public IQueryable<EmployeTraining> Get()
{
return ServiceEmploye.GetListeEmployes();
}
}
To call my service it works only through this URL: https://{server}/odata/v1/rh/employetraining
but I need to use this https://{server}/odata/v1/rh/employe-training
any help please.
For such scenario,change like below:
1.Change the entityset name:
public static class EdmModelBuilder
{
public static IEdmModel GetEdmModelEmployes()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<EmployeTraining>("employe-training");
return builder.GetEdmModel();
}
}
2.Add the attribute:
public class EmployeTrainingController : ODataController
{
[HttpGet]
[ODataRoute("employe-training")]
//[MyCustomQueryable()]
public IQueryable<EmployeTraining> Get()
{
return ServiceEmploye.GetListeEmployes();
}
}
3.Startup.cs:
app.UseMvc(routeBuilder=>
{
routeBuilder.Expand().Select().Count().OrderBy().Filter().MaxTop(null);
routeBuilder.MapODataServiceRoute("EmployeTraining", "odata/v1/rh", EdmModelBuilder.GetEdmModelEmploye());
});
Request the url:https://{server}/odata/v1/rh/employe-training
The Reason why is working using https://{server}/odata/v1/rh/employetraining is because is the Get method of the EmployeTrainingController Controller.
You should be able to change that behaibour if you modify the [HttpGet] on the Get method to [HttpGet("employe-training")]
I'm creating a custom model binder for a view model, implementing IModelBinder
I have a lot of properties in my view model, the majority of which do not need any custom binding. Rather than explicitly set all of the property values on my model individually from the ModelBindingContext, I would to be able to get the framework to bind the model for me, then I would carry out any custom binding:
public class ApplicationViewModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
// get .net core to bind values on model
// Cary out any customization of the models properties
bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
return Task.CompletedTask;
}
}
Basically I want to carry out the default model binding, then apply custom binding, similar to the approach taken in this SO post but for .NET Core, not framework.
I assumed applying the default binding would be straight forward, but haven't been able to find out how to do so. I believe the solution would involve ComplexTypeModelBinder and ComplexTypeModelBinderProvider classes, but can't seem to find out how to go about it.
I know I could just make any changes when the POST request hits my controller method, but this seem the wrong place and wrong time to do so.
For custom ComplexTypeModelBinder, you could inherit from ComplexTypeModelBinder.
Model
public class BinderModel
{
public int Id { get; set; }
public string Name { get; set; }
public string BinderValue { get; set; }
}
Controller Action
[HttpPost]
public void Post([FromForm]BinderModel value)
{
}
CustomBinder
public class CustomBinder : ComplexTypeModelBinder
{
private readonly IDictionary<ModelMetadata, IModelBinder> _propertyBinders;
public CustomBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders)
: base(propertyBinders)
{
_propertyBinders = propertyBinders;
}
protected override Task BindProperty(ModelBindingContext bindingContext)
{
if (bindingContext.FieldName == "BinderValue")
{
bindingContext.Result = ModelBindingResult.Success("BinderValueTest");
return Task.CompletedTask;
}
else
{
return base.BindProperty(bindingContext);
}
}
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
}
CustomBinderProvider
public class CustomBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++)
{
var property = context.Metadata.Properties[i];
propertyBinders.Add(property, context.CreateBinder(property));
}
//var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
//return new ComplexTypeModelBinder(propertyBinders, loggerFactory);
return new CustomBinder(propertyBinders);
}
return null;
}
}
Inject provider
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => {
options.ModelBinderProviders.Insert(0, new CustomBinderProvider());
});
}
ComplexTypeModelBinder has unfortunately been deprecated in .Net 5.0, and it's counterpart, ComplexObjectModelBinder, is sealed, so you can't extend from it.
But, you can work around that. ComplexObjectModelBinderProvider is public, and you can use that to create a ComplexObjectModelBinder. Thus, if you make your own custom IModelBinderProvider, you can have the constructor accept a ComplexObjectModelBinderProvider argument, and make use of that to make a ComplexObjectModelBinder. Then, you can pass that to your custom IModelBinder, where it'll happily do its custom work before falling back to the ComplexObjectModelBinder you supplied.
Here's an example. First, your IModelBinder. This example shows that you can use DI if you want to. (In this example, say we needed a DbContext.)
public class MyCustomModelBinder : IModelBinder
{
private readonly IModelBinder _defaultBinder;
private readonly DbContext _dbContext;
public MyCustomModelBinder(IModelBinder defaultBinder, DbContext dbContext)
{
_defaultBinder = defaultBinder;
_dbContext = dbContext;
}
public override Task BindModelAsync(ModelBindingContext bindingContext)
{
// -do custom work here-
return _defaultBinder.BindModelAsync(bindingContext);
}
}
However, in order to use DI on your custom model binder, you'll need a helper class. The problem is, when IModelBinderProvider is called, it won't have access to all the services in a typical request, like your DbContext for example. But this class will help:
internal class DIModelBinder : IModelBinder
{
private readonly IModelBinder _rootBinder;
private readonly ObjectFactory _factory;
public DIModelBinder(Type binderType, IModelBinder rootBinder)
{
if (!typeof(IModelBinder).IsAssignableFrom(binderType))
{
throw new ArgumentException($"Your binderType must derive from IModelBinder.");
}
_factory = ActivatorUtilities.CreateFactory(binderType, new[] { typeof(IModelBinder) });
_rootBinder = rootBinder;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var requestServices = bindingContext.HttpContext.RequestServices;
var binder = (IModelBinder)_factory(requestServices, new[] { _rootBinder });
return binder.BindModelAsync(bindingContext);
}
}
Now you're ready to write the custom IModelBinderProvider:
public class MyCustomModelBinderProvider : IModelBinderProvider
{
private readonly IModelBinderProvider _rootProvider;
public MyCustomModelBinderProvider(IModelBinderProvider rootProvider)
{
_rootProvider = rootProvider;
}
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(MyModel))
{
var rootBinder = _rootProvider.GetBinder(context)
?? throw new InvalidOperationException($"Root {_rootProvider.GetType()} did not provide an IModelBinder for MyModel.");
return new DIModelBinder(typeof(MyCustomModelBinder), rootBinder);
}
return null;
}
}
Finally, in your startup file where you configure services, you can grab the ComplexObjectModelBinderProvider instance, use that to create a new instance of your MyCustomModelBinderProvider, and insert that into the ModelBinderProviders.
services.AddMvc(options =>
{
var fallbackProvider = options.ModelBinderProviders
.First(p => p is ComplexObjectModelBinderProvider);
var myProvider = new MyCustomModelBinderProvider(fallbackProvider);
options.ModelBinderProviders.Insert(0, myProvider);
})
im building a site using MVC4 and i want to display a navigation bar at the top of my _ViewStart according to my Database.
How can i do so? can i use a contoroller ActionResult that fired once the index page is loaded?
or how can i triger it by a partial view
my current ActionResult returning partial view is:
public ActionResult NavigationBar()
{
var entities = new CakesDBEntities();
var articles = entities.Articles;
List<NavBarModel> navBarList = articles.Select(nb => new NavBarModel { Title = nb.title, Url = nb.url }).ToList();
return View(navBarList);
}
my model:
namespace SimplyCakes20131009.Models
{
public class NavBarModel
{
public string Title { get; set; }
public string Url { get; set; }
}
}
my partial view:
#model IEnumerable<SimplyCakes20131009.Models.NavBarModel>
#foreach (var bar in Model)
{
<li>
#Html.ActionLink(bar.Title, bar.Url)
</li>
}
How can i integrate the nav bar to my _ViewStart?
A better option would be to use the _Layout.cshtml. _ViewStart is just calls the _Layout.cshtml.
You probably don't need partial View here. You can use a Child Action that renders PartialView results.
In your
_Layout.cshtml :
You can have
#{ Html.RenderAction("Navigation", "Home"); }
This points to the HomeController and Navigation Action
Additional Note: Html.RenderAction better because it is much faster than the Html.Action.
It can handle large amount of HTML efficiently as it will directly send the result to the Response. Html.Action just returns a strings with the result.
Navigation Action has its Navigation View which is pretty much equivalent to what you had in your view.
Home/Navigation.cshtml :
#model IEnumerable<MvcApplication1.Controllers.NavViewModel>
#foreach (var nav in Model)
{
<li>#Html.ActionLink(nav.Title, nav.Url)</li>
}
HomeController.cs :
Note that you probably inject the DB access as dependency to support the testability.
public class HomeController : Controller
{
private readonly ICakesRepository _cakesRepository;
//additional constructor to support testability.
public HomeController(ICakesRepository cakesRepository) {
_cakesRepository = cakesRepository;
}
//this can be removed if you the above with IOC/DI wire-up
public HomeController() {
_cakesRepository = new CakesRepository();
}
[ChildActionOnly]
[HttpGet]
public ActionResult Navigation() {
var articles = _cakesRepository.GetArticles();
var navBarList = articles.Select(nb => new NavViewModel { Title = nb.Title, Url = nb.Url });
return PartialView(navBarList);
}
}
Additional supporting classes :
public class NavViewModel {
public string Title { get; set; }
public string Url { get; set; }
}
public interface ICakesRepository {
IEnumerable<Articles> GetArticles();
}
public class CakesRepository : ICakesRepository {
public IEnumerable<Articles> GetArticles() {
//call to a db
//fake db data
return new List<Articles>() {
new Articles(){Title = "Title1", Url = "http://urlone.com"},
new Articles(){Title = "Title2", Url = "http://urltwo.com"},
new Articles(){Title = "Title3", Url = "http://urlthree.com"}
};
}
}
public class Articles {
public string Title { get; set; }
public string Url { get; set; }
}
Thanks for looking.
This is a trivial task when using a normal (not WebAPI) action filter as I can just alter the filterContext.Result property like so:
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary { { "controller", "Home" }, {"action", "Index" } });
Unfortunately, I have to use HttpActionContext for WebAPI, so I can not access filterContext.Result.
So what should I do in place of that? I have the filter set up and it does execute at the appropriate time, I just don't know how to make it prevent execution of the requested service endpoint and instead point to a different one.
Here is my controller:
[VerifyToken]
public class ProductController : ApiController
{
#region Public
public List<DAL.Product.CategoryModel> ProductCategories(GenericTokenModel req)
{
return HelperMethods.Cacheable(BLL.Product.GetProductCategories, "AllCategories");
}
public string Error() //This is the endpoint I would like to reach from the filter!
{
return "Not Authorized";
}
#endregion Public
#region Models
public class GenericTokenModel
{
public string Token { get; set; }
}
#endregion Models
}
Here is my filter:
using System.Web.Http.Controllers;
using ActionFilterAttribute = System.Web.Http.Filters.ActionFilterAttribute;
namespace Web.Filters
{
public class VerifyTokenAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext filterContext)
{
dynamic test = filterContext.ActionArguments["req"];
if (test.Token != "foo")
{
//How do I redirect from here??
}
base.OnActionExecuting(filterContext);
}
}
}
Any help is appreciated.
The answer in my case was simply to change the Response property of the filterContext rather than to redirect to a different endpoint. This achieved the desired result.
Here is the revised filter:
public class VerifyTokenAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext filterContext)
{
dynamic test = filterContext.ActionArguments["req"];
if (test.Token != "foo")
{
filterContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
base.OnActionExecuting(filterContext);
}
}