I'm starting a new ASP.NET project after a few years developing in MVC4, and I have a question regarding architecture.
At the top corner of each page, I will display details of the current logged in user.
In MVC4 I achieved something like this by creating a BaseController, which created an EF data connection, and set up some common variables that would be used on every page - CurrentUser being one of them.
Now that I'm using Core, this approach doesn't seem to work, and certainly isnt mockable.
What would be the correct way to achieve something like this via ASP.NET Core?
I need the same variables on every view, and certainly dont want to have to write the code in each controller action!
You can use View Components feature in asp.net core to implement that functionality.
//In your ConfigureServices method , add your services that will be injected whenever view component is instantiated
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton<IUserRespository, UserRepository>();
}
//Now Create a view component
public class LoggedInUser : ViewComponent
{
private IUserRespository userRepository;
//Services can be injected using asp.net core DI container
public LoggedInUser(IUserRepository userRepository,SomeOtherService service)
{
//assign services to local variable for use later
this.userRepository = userRepository;
}
//This method can take any number of parameters and returns view
public async Task<IViewComponentResult> InvokeAsync(int param1,string param2,etc )
{
//get the logged in user data here using available services
var loggedInUserData = GetSomeData(context);
return View(loggedInUserData );
}
}
Create view file # View/Shared/Components/LoggedInUser/Default.cshtml.View can be strongly typed.
#model LoggedInUserModel
<div>
<!-- html here to render model -->
</div>
Now, since you use to display this data on every page , you need to apply _Layout.chstml to all your pages . In the _Layout.chstml , you can render view component defined above with any additional parameter you would like to pass as anonymous type.
#await Component.InvokeAsync("LoggedInUser", new { param1=value,param2=value,etc })
Testing the View Component:
var mockRepository = Mock of ICityRepository;
var viewComponent= new LoggedInUser(mockRepository);
ViewViewComponentResult result
= viewComponent.Invoke() as ViewViewComponentResult; //using Invoke here instead of InvokeAsnyc for simplicity
//Add your assertions now on result
Note :
It is also possible to decorate a controller with [ViewComponent(Name = "ComponentName")] attribute and define public IViewComponentResult Invoke()
or public IViewComponentResult InvokeAsync() to turn them in to hybrid controller - view component.
Related
We have a running IS4 instance for corporate customers (built on top of QuickUI), now the mgmt wants to extend it with another client and they want to have a special layout and page adjustments when the authorization process comes from this client (in essence we need to collect additional info in login form and it has to be branded slightly differently).
Now, adjusting the form and visual appearance is not a problem, but deciding when to show it is proving a challenge. We have our templating and branding in _Layout which is used by all the pages, and I somehow need to know inside _layout, if the page load is part of the authentication context and if so, which one.
The way QuickUI did it in AccountController, is using IS interaction, which generates AuthenticationRequest which contains the client:
var context = await interaction.GetAuthorizationContextAsync(returnUrl);
var clientName = context.Client?.ClientName;
This fits nicely with AccountController logic and works just fine for authentication purposes, but does little for visual. I do not have the returnUrl inside _Layout (and layout is used by other pages, for registration, etc, so I cannot really cram specific viewmodels into it) and apparently no clean way to determine the context.
a) I could start digging through HttpRequest and parsing request URLs, fishing for returnUrls, but I would first like to see if there is a more streamlined, or even existing solution.
b) Another option is to have multiple layout files, and expose Client to the ViewModel and switch layouts based on it.
Ideally however, I would like something "clean" and maintainable, a service I could #inject into _Layout.cshtml and just do #if (contextService.Client == ...)
Has someone seen or done something like it?
Until (and if) someone posts a "cleaner" solution (especially the one that does not revolve around returnUrl magic string), this is the workaround I implemented. It's dirty, I admit, it pokes on HttpContext from a service, which is not really asp.net core way, but at this point, I see no other way to obtain the information I need to construct authorization context:
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityServer4.Services;
using Microsoft.AspNetCore.Http;
namespace MyServices
{
public interface IContextService
{
Task<AuthorizationRequest> GetContextAsync();
}
public class ContextService : IContextService
{
private readonly IIdentityServerInteractionService interaction;
private readonly IHttpContextAccessor contextAccessor;
private AuthorizationRequest context;
public ContextService(IIdentityServerInteractionService interaction, IHttpContextAccessor contextAccessor)
{
this.interaction = interaction;
this.contextAccessor = contextAccessor;
}
public async Task<AuthorizationRequest> GetContextAsync() => context ??= await InternalObtainContextAsync();
private async Task<AuthorizationRequest> InternalObtainContextAsync()
{
var query = contextAccessor.HttpContext?.Request.Query;
if (query == null || query.Count == 0 || !query.ContainsKey("returnUrl")) return new AuthorizationRequest(); // empty context
return await interaction.GetAuthorizationContextAsync(query["returnUrl"]);
}
}
}
Register in startup with services.AddTransient<IContextService, ContextService>(); and then you can inject it into any page and/or layout:
#inject IContextService contextService;
#{
// render only when invoked from authorization context
var context = await contextService.GetContextAsync();
if (context.Client != null)
{
<div>Hi, I am inside authorization context for client #context.Client.ClientId</div>
}
}
I'm writing an app using WebAssembly Blazor hosted by ASP.NET Core. Some of pages are implemented in Blazor, but some old pages are still ASP.NET Core Razor views. I need to create a link in Blazor component pointing to action of controller on server.
I can write:
NavigationManager.NavigateTo("SomeContoller/SomeAction/123", true)
But I don't want to hardcode url to action, because changing server routing or contoller/action names will break such links. Is there any way to create proper links via some helper, similar to ASP.Net Core UriHelper? Like:
UriHelper.Action("SomeAction", "SomeController", new {id = 123});
In Blazor server apps you can use LinkGenerator. The usage is not much different that of UriHelper:
#using Microsoft.AspNetCore.Routing
#inject LinkGenerator LinkGenerator
Sign in
ReSharper understands this one too, so you will get auto-completion for controller and action names.
In WebAssembly apps LinkGenerator is not available, so your best bet is to dump all routes from the server and implement your own link generator which uses that data on the client (its complexity depends on complexity of your routes, the one from ASP.NET Core is quite complex).
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Routing;
namespace BlazorTest.Server.Controllers
{
[Route("api/routes")]
[ApiController]
public class RouteInformationController : ControllerBase
{
private readonly EndpointDataSource _endpointDataSource;
public RouteInformationController(EndpointDataSource endpointDataSource)
{
_endpointDataSource = endpointDataSource;
}
public IEnumerable<object> Get()
{
foreach (var endpoint in _endpointDataSource.Endpoints.OfType<RouteEndpoint>())
{
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
if (actionDescriptor == null)
continue;
yield return new
{
actionDescriptor.ControllerName,
actionDescriptor.ActionName,
Parameters = actionDescriptor.Parameters.Select(p => p.Name),
RoutePattern = endpoint.RoutePattern.RawText,
};
}
}
}
}
You can create a static class with constant properties in it for all the URLs that you use in the app. After that use the same static class property in both the page route and your navigation route. Below is a very basic version of this:
public static class RouteUrls
{
public static string Home = "/Home";
public static string ProductList = "/Product";
public static string ProductDetail = "/Product/Detail";
public static string SomePage = "/SomeContoller/SomeAction";
}
// to access it use like this:
NavigationManager.NavigateTo($"{RouteUrls.SomePage}/123", true)
Upgrading to asp.net core 2.2 in my hobby project there is a new routing system I want to migrate to. Previously I implemented a custom IRouter to be able to set the controller for the request dynamically. The incoming request path can be anything. I match the request against a database table containing slugs and it looks up the a matching data container class type for the resolved slug. After that I resolve a controller type that can handle the request and set the RouteData values to the current HttpContext and passing it along to the default implementation for IRouter and everything works ok.
Custom implementaion of IRouter:
public async Task RouteAsync(RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value;
var page = _pIndex.GetPage(requestPath);
if (page != null)
{
var controllerType = _controllerResolver.GetController(page.PageType);
if (controllerType != null)
{
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
newRouteData.Values["pageType"] = page.PageType;
newRouteData.Values["controller"] = controllerType.Name.Replace("Controller", "");
newRouteData.Values["action"] = "Index";
context.RouteData = newRouteData;
await _defaultRouter.RouteAsync(context);
}
}
}
A controller to handle a specific page type.
public class SomePageController : PageController<PageData>
{
public ActionResult Index(PageData currentPage)
{
return View("Index", currentPage);
}
}
However I got stuck when I'm trying to figure out how I can solve it using the new system. I'm not sure where I'm suppose to extend it for this behavior. I don't want to turn off the endpoint routing feature because I see an opportunity to learn something. I would aso appreciate a code sample if possible.
In ASP.NET 3.0 there is an new dynamic controller routing system. You can implement DynamicRouteValueTransformer.
Documentation is on the way, look at the github issue
I have an action in my ASP.Net Core WebAPI Controller which takes one parameter. I'm trying to configure it to be able to call it in following forms:
api/{controller}/{action}/{id}
api/{controller}/{action}?id={id}
I can't seem to get the routing right, as I can only make one form to be recognized. The (simplified) action signature looks like this: public ActionResult<string> Get(Guid id). These are the routes I've tried:
[HttpGet("Get")] -- mapped to api/MyController/Get?id=...
[HttpGet("Get/{id}")] -- mapped to api/MyController/Get/...
both of them -- mapped to api/MyController/Get/...
How can I configure my action to be called using both URL forms?
if you want to use route templates
you can provide one in Startup.cs Configure Method Like This:
app.UseMvc(o =>
{
o.MapRoute("main", "{controller}/{action}/{id?}");
});
now you can use both of request addresses.
If you want to use the attribute routing you can use the same way:
[HttpGet("Get/{id?}")]
public async ValueTask<IActionResult> Get(
Guid id)
{
return Ok(id);
}
Make the parameter optional
[Route("api/MyController")]
public class MyController: Controller {
//GET api/MyController/Get
//GET api/MyController/Get/{285A477F-22A7-4691-AA51-08247FB93F7E}
//GET api/MyController/Get?id={285A477F-22A7-4691-AA51-08247FB93F7E}
[HttpGet("Get/{id:guid?}"
public ActionResult<string> Get(Guid? id) {
if(id == null)
return BadRequest();
//...
}
}
This however means that you would need to do some validation of the parameter in the action to account for the fact that it can be passed in as null because of the action being able to accept api/MyController/Get on its own.
Reference Routing to controller actions in ASP.NET Core
I'm using a simple CMS to build a website and have a requirement to inject 'components' into my pages. At present, I am rendering out components in a fairly simple way such as follows:
<latest-blog-posts limit="3" order-by="date asc"/>
I then want to use TagHelpers to fill in the real content. Is there a way to call a controller action in ASP.NET Core and replace the invoking tag with the resulting view. My feeling is that I need to be looking at using output.WriteTo(TextWriter writer, HtmlEncoder encoder), but I'm stuck on how to feed the response from an Action into the writer.
You're looking for view components. They're similar in function to the way child actions used to work in MVC 5 and previous.
public class LatestBlogPostsViewComponent : ViewComponent
{
private readonly BlogDbContext db;
public LatestBlogPostsViewComponent(BlogDbContext context)
{
db = context;
}
public async Task<IViewComponentResult> InvokeAsync(int limit, string orderBy)
{
var posts = // get latest posts;
return View(posts);
}
}
Next, create the view Views/Shared/Components/LatestBlogPosts.cshtml and add the code you'd like to render your list of latest blog posts. Your view's model will be the type of posts returned by the view component.
Finally, in the view or layout where you want the latest blog posts to be rendered call the component using:
#await Component.InvokeAsync("LatestBlogPosts", new { limit = 3, orderBy = "date asc" })
Or, if you prefer the TagHelper syntax:
<vc:latest-blog-posts limit="3" orderBy="date asc"></vc:latest-blog-posts>
The latter method requires that you register the view component(s).