MVC 5 how to specify constraints using attribute routing - attributerouting

I'm trying to do full attribute routing, without conventional routing, and trying to tell my routes which area they belong to based on the current domain.
In convention routing, I can specify my object constraints with this as an example:
context.MapRoute(
"MyRouteName",
"admin/sign-in",
new { controller="AdminController", action="SignIn" },
new { SitePermitted = new SiteConstraint("Admin") } // <-- how do I do this exact line of code but in attribute routing
);
Where SiteConstraint inherits from IRouteConstraint. How do I do the same thing, but using attribute routing? I am looking for something like this:
[AreaName("Admin")]
[Route("admin/sign-in")]
[new SiteConstraint("Admin")]
public ActionResult SignIn(...) {...}
Where MyConstraint has a Match method that gets the current http request and if its domain is "myadmindomain.com", then Match method returns true, and MVC executes this route given that the user is on myadmindomain.com/admin/sign-in.

What you want to do requires the use of the class RouteFactoryAttribute, in MVC 5 inhering from that class you can use your SiteConstraint but using attribute routing. So you can have something like:
public class SiteRouteAttribute : RouteFactoryAttribute
{
public SiteRouteAttribute (string template, string sitePermitted) : base(template)
{
SitePermitted = sitePermitted;
}
public override RouteValueDictionary Constraints
{
get
{
var constraints = new RouteValueDictionary();
constraints.Add("site", new SiteConstraint(SitePermitted));
return constraints;
}
}
public string SitePermitted
{
get;
private set;
}
}
Then in your controller you can have:
[SiteRoute("somepath/{somevariable}/{action=Index}", "Admin")]
public class MyController : Controller
{
public ActionResult Index()
{
....
}
}
Take a look at Jon Galloway's post which has a workable example

Related

Passing parent component to all child components in blazor via rendertree

I created an custom form component in blazor and inherited from the default EditForm component to add some functionality.
public class CustomForm : EditForm
I want to pass the instance of the form component to all its children, so a child can retrieve it via a cascading parameter like so
[CascadingParameter]
public CustomForm Form { get; set; }
I took over the BuildRenderTree Method of the default Editform
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
Debug.Assert(EditContext != null);
// If _editContext changes, tear down and recreate all descendants.
// This is so we can safely use the IsFixed optimization on CascadingValue,
// optimizing for the common case where _editContext never changes.
builder.OpenRegion(EditContext.GetHashCode());
builder.OpenElement(0, "form");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "onsubmit", _handleSubmitDelegate);
builder.OpenComponent<CascadingValue<EditContext>>(3);
builder.AddAttribute(4, "IsFixed", true);
builder.AddAttribute(5, "Value", EditContext);
builder.AddAttribute(6, "ChildContent", ChildContent?.Invoke(EditContext));
builder.CloseComponent();
builder.CloseElement();
builder.CloseRegion();
}
But i do not know how to manipulate this code to achieve my goal.
It's possible to pass any object as cascading value so you can override the BuildRenderTree like this and the CustomForm instance will be passed to the child components. Explicit cast to RenderFragment is needed to create delegate of this type, otherwise CascadingValue<>.SetParametersAsync will throw an invalid cast exception.
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;
namespace BlazorTest
{
public class CustomForm : EditForm
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenRegion(0);
builder.OpenComponent<CascadingValue<CustomForm>>(1);
builder.AddAttribute(2, "Value", this);
builder.AddAttribute(3, "ChildContent", (RenderFragment)base.BuildRenderTree);
builder.CloseComponent();
builder.CloseRegion();
}
}
}
I tested this with these components:
CustomFormComponent.razor
<h3>CustomFormComponent</h3>
#code {
private CustomForm _form;
[CascadingParameter]
public CustomForm Form
{
get => _form;
set
{
_form = value;
Console.WriteLine("CustomForm set in CustomFormInput");
}
}
}
TestPage.razor
#page "/test"
<CustomForm Model=model>
<CustomFormComponent />
</CustomForm>
#code {
private AModel model = new();
private class AModel
{
public string Name { get; set; }
}
}
I just want to add that I'm not an expert in this and I'm not sure if you should do it this way. Maybe it would be better to use EditForm as a child component of the CustomForm because you don't have to worry about BuildRenderTree method when using razor markup.
<CascadingValue Value=this>
<EditForm Model=model>
#ChildContent
</EditForm
</CascadingValue>

ViewModels references in ShellViewModel Caliburn.Micro

In this thread : Can anybody provide any simple working example of the Conductor<T>.Collection.AllActive usage? I've had part of an answer but I'm still a but confused.
I would simply like to reference all my view models into my ShellViewModel to be able to open/close ContentControls, but without injecting all of them in the constructor.
In the answer, it is suggested to inject an interface in the constructor of the ShellViewModel. If I do that, do I have to inject all my ViewModels in a class that implements that interface?
public MyViewModel(IMagicViewModelFactory factory)
{
FirstSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
SecondSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
ThirdSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
Items.Add(FirstSubViewModel);
Items.Add(SecondSubViewModel);
Items.Add(ThirdSubViewModel);
}
Also, I would like to avoid going through IoC.Get<> to get the instances of my view Models, I think it violates the principles of IoC if I am not mistaken.
In a few other examples, they create new viewModels when needed, but what's the point of using IoC in that case, especially when I need services injected inside those new ViewModels?
In my Shell view, I have a layout with 3 different areas, bound to my shell view model by :
<ContentControl x:Name="Header"
Grid.ColumnSpan="3"/>
<ContentControl x:Name="Menu"
Grid.Row="1"/>
<ContentControl x:Name="Main"
Grid.ColumnSpan="3"/>
In my ShellViewModel extending Conductor.Collection.AllActive, I reference the 3 areas like this:
public Screen Menu { get; private set; }
public Screen Header { get; private set; }
public Screen Main { get; private set; }
I would like to be able to change them like so:
Menu = Items.FirstOrDefault(x => x.DisplayName == "Menu");
Header = Items.FirstOrDefault(x => x.DisplayName == "Header");
Main = Items.FirstOrDefault(x => x.DisplayName == "Landing");
All my ViewModels have a DisplayName set in their constructor.
I have tried this but GetChildren() is empty
foreach (var screen in GetChildren())
{
Items.Add(screen);
}
Am I missing something obvious?
Thanks in Advance!
Finally, I managed to find an answer myself. It's all in the AppBootstrapper!
I ended up creating a ViewModelBase for my Screens so that they could all have an IShell property (so that the ViewModels could trigger a navigation in the ShellViewModel) like so:
public class ViewModelBase : Screen
{
private IShell _shell;
public IShell Shell
{
get { return _shell; }
set
{
_shell = value;
NotifyOfPropertyChange(() => Shell);
}
}
}
then in the AppBoostrapper registered them like this :
container.Singleton<ViewModelBase, SomeViewModel>();
container.Singleton<ViewModelBase, AnotherViewModel>();
container.Singleton<ViewModelBase, YetAnotherViewModel>();
then created an IEnumerable to pass as param to my ShellViewModel ctor:
IEnumerable<ViewModelBase> listScreens = GetAllScreenInstances();
container.Instance<IShell>(new ShellViewModel(listScreens));
then passing the IShell to each ViewModels
foreach (ViewModelBase screen in listScreens)
{
screen.Shell = GetShellViewModelInstance();
}
for the sake of completeness, here are my GetAllScreenInstances() and GetShellViewModelInstance() :
protected IEnumerable<ViewModelBase> GetAllScreenInstances()
{
return container.GetAllInstances<ViewModelBase>();
}
protected IShell GetShellViewModelInstance()
{
var instance = container.GetInstance<IShell>();
if (instance != null)
return instance;
throw new InvalidOperationException("Could not locate any instances.");
}
Here's what my ShellViewModel ctor looks like:
public ShellViewModel(IEnumerable<ViewModelBase> screens )
{
Items.AddRange(screens);
}
Hope this can help someone in the future!

ASP.NET Web Api Routing (IIS vs Self Hosted)

I have found a minor difference in the routing base classes in ASP.NET Web Api which has forced me to write a little helper class which will allow me to define my routes just once. Is there a reason for this? I'm assuming it was too big a change to the framework to make both RouteCollections derive from the same base class or implement the same interface (which would have made this class much simpler)
public static class RouteMapper
{
private class Route
{
public string Name { get; set; }
public string Template { get; set; }
public object Defaults { get; set; }
public Route(string name, string template, object defaults)
{
Name = name;
Template = template;
Defaults = defaults;
}
}
private static List<Route> GetRoutes()
{
return new List<Route>
{
new Route(
"API Default",
"api/{controller}/{id}",
new {id = RouteParameter.Optional})
};
}
public static void AddHttpRoutes(this HttpRouteCollection routeCollection)
{
var routes = GetRoutes();
routes.ForEach(route => routeCollection.MapHttpRoute(route.Name, route.Template, route.Defaults));
}
public static void AddHttpRoutes(this RouteCollection routeCollection)
{
var routes = GetRoutes();
routes.ForEach(route => routeCollection.MapHttpRoute(route.Name, route.Template, route.Defaults));
}
}
What this allows me to do is to call a simple AddHttpRoutes method in both my Global.asax and my integration tests.
Integration Tests
var configuration = new HttpSelfHostConfiguration("http://localhost:20000");
configuration.Routes.AddHttpRoutes();
_server = new HttpSelfHostServer(configuration);
_server.OpenAsync().Wait();
Global.asax
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.AddHttpRoutes();
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Is this a known issue and is it likely to be fixed in a later release of ASP.NET Web Api?
Yes, the routing is slightly different due to the fact that ASP.NET already has routing but we couldn'd depend on it directly since that would prevent Self-host support. We're still looking at how things could make more sense.

How do I find the output model type in a behavior?

With FubuMVC, I'm not sure what the best way is to determine the current action's output model type. I see different objects that I could get the current request's URL from. But that doesn't lead to a very good solution.
What's the easiest way to get the current action's output model type from the behavior?
If this isn't a good practice, what's a better way?
First, I'm assuming you've already got your settings object(s) set up in StructureMap and have the ISettingsProvider stuff already wired up.
The best, simplest thing to do would be just to pull the settings in the view, like this:
<%: Get<YourSettingsObject>().SomeSettingProperty %>
If you insist on having these be a property on your output model, then continue reading:
Let's say you had a settings object like this:
public class OutputModelSettings
{
public string FavoriteAnimalName { get; set; }
public string BestSimpsonsCharacter { get; set; }
}
Then you had an output model like this:
public class OutputModelWithSettings
{
public string SomeOtherProperty { get; set; }
public OutputModelSettings Settings { get; set; }
}
You'll need to do a few things:
Wire up StructureMap so that it will do setter injection for Settings objects (so it will automatically inject the OutputModelSettings into your output model's "Settings" property.
Set up a setter injection policy in your StructureMap initialization code (a Registry, Global ASAX, your Bootstrapper, etc -- wherever you set up your container).
x.SetAllProperties(s => s.Matching(p => p.Name.EndsWith("Settings")));
Create your behavior to call StructureMap's "BuildUp()" on the output model to trigger the setter injection. The behavior will be an open type (i.e. on the end) so that it can support any kind of output model
public class OutputModelSettingBehavior<TOutputModel> : BasicBehavior
where TOutputModel : class
{
private readonly IFubuRequest _request;
private readonly IContainer _container;
public OutputModelSettingBehavior(IFubuRequest request, IContainer container)
: base(PartialBehavior.Executes)
{
_request = request;
_container = container;
}
protected override DoNext performInvoke()
{
BindSettingsProperties();
return DoNext.Continue;
}
public void BindSettingsProperties()
{
var viewModel = _request.Find<TOutputModel>().First();
_container.BuildUp(viewModel);
}
}
Create a convention to wire up the behavior
public class OutputModelSettingBehaviorConfiguration : IConfigurationAction
{
public void Configure(BehaviorGraph graph)
{
graph.Actions()
.Where(x => x.HasOutput &&
x.OutputType().GetProperties()
.Any(p => p.Name.EndsWith("Settings")))
.Each(x => x.AddAfter(new Wrapper(
typeof (OutputModelSettingBehavior<>)
.MakeGenericType(x.OutputType()))));
}
}
Wire the convention into your FubuRegistry after the Routes section:
ApplyConvention<OutputModelSettingBehaviorConfiguration>();
In your view, use the new settings object:
<%: Model.Settings.BestSimpsonsCharacter %>
NOTE: I have committed this as a working sample in the FubuMVC.HelloWorld project in the Fubu source. See this commit: https://github.com/DarthFubuMVC/fubumvc/commit/2e7ea30391eac0053300ec0f6f63136503b16cca

asp mvc roles from httpcontext

i want to pass current area name to authorization attribute, like:
[SexyAuthorize(Roles = Url.RequestContext.RouteData.Values["area"])]
public class FormsController : Controller
{
}
but Url is member of controller. how can i pass it other way?
i know that i can use User.InRole in each method, but i want do it for class. thx.
You can't pass dynamic values to an attribute like this. All values passed to an attribute in .NET need to be known at compile time. One possible workaround is to fetch this value in your custom implementation of the attribute as you have access to the HTTP context.
Something like:
[SexyAuthorize(RolesRouteParamName = "area")]
public class FormsController : Controller
{
...
}
and then:
public SexyAuthorizeAttribute: AuthorizeAttribute
{
public string RolesRouteParamName { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
var roles = httpContext.Request.RequestContext.RouteData.Value[RolesRouteParamName];
// TODO: continue with the implementation...
...
}
}