I have encountered an issue with my new test project when I inherit a razor page from another.
So I had an editor page and I wanted to use the same flow just with gave an extra param (id), the code was:
public class EditModel : BaseModel
{
public EditModel()
{
}
public Task<IActionResult> OnPostAsync(int id)
{
...
}
}
public class CreateModel : EditModel
{
public CreateModel()
{
}
public Task<IActionResult> OnPostAsync()
{
...
}
}
Also I defined the editor cshtml as:
#page "{id:int}"
...
and the create cshtml as:
#page
...
I would expect that the routing was obvious cuz editor's parameter is NOT optional, but create does not need a parameter, but I got the error:
Multiple handlers matched
If I define parameter in create page model too it starts working with 0 value.
I have two questions about that:
Can I define a routing template where I can explicitly forbid the parameter (and how) to avoid ambiguity? (Now I'm using default routing template.)
Shoud I avoid this kind of inheritance of razor pages? I can except that if it is by design and use another way to do it.
I would like to mention that I know I can define another handler methods different from default onget/onpost/etc and use it this way, however I think the above problem should work properly if I can define the routing in a good way.
Hi and welcome to the board! Before digging more about your purposes for this design, let first make something clear about the scenario.
You are probably using ASP.NET Core Razor Page, but you ended up with this line EditModel : BaseModel so I assume that there must be something like BaseModel : PageModel
Next thing I know that you make another inheritance which is CreateModel : EditModel and this time it bugged me out because this obviously will make EditModel inherit a method so-called Task<IActionResult> OnPostAsync(int id) because that what inheritance do!
For example:
public class Test1
{
public void sayHello(){
Console.WriteLine("hehe");
}
}
public class Test2 : Test1
{
public void sayHello(string inputValue){
Console.WriteLine(inputValue);
}
}
and the result when you have something like
Test2 test = new Test2();
test.sayHello();
test.sayHello("something");
The result would be
hehe
something
Once again, I'm not sure about your purposes on this methodology, so it would be nice if you can share some though so we both can evolve in something new.
UPDATE:
At this point, I understand that you created a problem for a case study. So let see if I came up with something right.
.NET core allows you to define some specific route inside Startup.cs beside the default routing, which obviously won't fit in this case.
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AddPageRoute("/edit", "{handler?}/{id?}");
options.Conventions.AddPageRoute("/create", "{handler?}");
});
}
Now, I believe if you put #page "{id:int}" in your .cshtml page, it would run as expected.
Related
We have a bunch of endpoints where we'd like to do the exact same thing for each of them:
Register them as a route and verify that the user has access to them. Very condensed our issue can be condensed to us having something like this:
[HttpGet, Route(EntityId.First)]
[HttpGet, Route(EntityId.Second)]
[VerifyAccessFilter(EntityId.First, EntityId.Second)]
public async Task<IActionResult> Endpoint()
{
return Ok();
}
But would much rather like something like:
[RouteAndVerify(EntityId.First, EntityId.Second)]
public async Task<IActionResult> Endpoint()
{
return Ok();
}
As you can tell this is very simplified, but I hope the intent gets across.
The hard part seems to be registering the route without using the default Route-attribute.
You can achieve this with a custom IActionModelConvention implementation. The official documentation explains the concept of an action model convention: Work with the application model in ASP.NET Core - Conventions. In a nutshell, by implementing IActionModelConvention, you can make changes to the application model and add filters, routes, etc to an action at runtime.
This is best explained with a sample implementation, which follows below. As you want to combine your existing MVC filter with the ability to configure routes for an action, the implementation below implements both IResourceFilter (this can be whatever filter type you're using) and IActionModelConvention:
public class VerifyAccessFilterAttribute : Attribute, IActionModelConvention, IResourceFilter
{
public VerifyAccessFilterAttribute(params string[] routeTemplates)
{
RouteTemplates = routeTemplates;
}
public string[] RouteTemplates { get; set; }
public void Apply(ActionModel actionModel)
{
actionModel.Selectors.Clear();
foreach (var routeTemplate in RouteTemplates)
{
actionModel.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel { Template = routeTemplate },
ActionConstraints = { new HttpMethodActionConstraint(new[] { "GET" }) }
});
}
}
public void OnResourceExecuting(ResourceExecutingContext ctx) { ... }
public void OnResourceExecuted(ResourceExecutedContext ctx) { ... }
}
In this example, it's all about the Apply method, which simply adds a new SelectorModel for each routeTemplate (as I've named it), each of which is constrained to HTTP GET requests.
Generally speaking, you cannot "merge" attributes, because attributes do not execute code. Attributes are only markers. Like "this method is marked red and blue". Then other code will come along, one looking for all red marks and doing something and another looking for all blue marks and doing something else. Building a purple mark by merging red and blue is just going to confuse the code looking for the markup, because purple is neither red nor blue.
However, AOP (aspect oriented programming) is available from third parties for C# and means attributes (called aspects because they do more than the normal marker attributes) can execute code.
You could write an aspect that decorates the method it's sitting on with the attributes you need, so you can write it once (and test it) and then you can set it on every method without worrying about forgetting an attribute or setting it wrong.
There are multiple AOP providers for C#, the most popular one seems to be PostSharp. You can see how write an aspect that adds attributes to a class or method at compile time with PostSharp here.
I have a quick question,
In so many examples I saw controller Actions which does not return any View or Partialview are also added in the same controller - Now in my situation i have multiple controllers which i need to run same ACTIONS such as
Controller A will Run Action A
Controller B will run Action A
Adding Action A in both Controller A and B does not look right.
As I am making [HttpPost] calls for Action A using AJAX, therefore, i want this to be in a Controller for easy POST access, although I don't want to add the same Action in both Controllers
I can add action A in Controller A and let Controller B access the same action requesting from Controller A instead but what I was thinking
**How about if I create a new Controller called
commonActionContoller
and put "ACTION A" in it and let everything use commonActionContoller when Action A is required?**
Cheers
EDIT: Example added as following
An example application which has Person and Countries, So Person model is the same for all Countries but we have different Controller for each country so If an Admin wants to update Person's model with field IsEmpoyed from true to false then they go to for example {USA}/Index controller and switch true to false. Now, this is same for {AUS}/Index and {China}/Index so Action which changes IsEmpyed to true/false is the same across all controllers. To make this work i don't want to add Action IsEmplyed to all country controllers - (Couldn't think of a better example) –
You should write Action A in both Controller. Otherwise it will violate Single responsibility principle. Best practice is to move the implementation codes from Controller to a Service layer. For example, if you want to load Product Category for Products and Sub-Categories, then code will be like this:
public interface ICategoryService
{
List<Category> LoadCategory();
}
public class CategoryService : ICategoryService
{
public List<Category> LoadCategory()
{
//code here
}
}
public class ProductController : Controller
{
private readonly ICategoryService _categoryService;
public ProductController()
{
_categoryService = <inject dependency here>;
}
public ActionResult GetCategory()
{
var category = _categoryService.LoadCategory();
}
}
public class SubCategoryController : Controller
{
private readonly ICategoryService _categoryService;
public SubCategoryController()
{
_categoryService = <inject dependency here>;
}
public ActionResult GetCategory()
{
var category = _categoryService.LoadCategory();
}
}
The guiding principle here should be Separation Of Concerns.
If ControllerA and ControllerB have specific business logic, and adding a CommonActions controller give shared data a good isolated home this is a good practice.
Without a better illustration of your needs though it's difficult to answer.
A slightly better example might be the order application:
InventoryController
EmployeeController
You probably don't want a CommomController with methods like:
GetStoreClosingHours(int storeNumber);
GetTotalSales(int employeeId);
GetEmployeeComps(int employeeId);
IoC and dependency injection might pay off as well depending on the actions. Where any controller could call methods like:
GetLastLogonTime(thisEmployee);
It's really a set of principles designing your application after all, and best practices aren't always super neatly packaged. I'd say most importantly choose something flexible, scalable and then stick with it.
I am trying to inherit Route attributes from a base Controller exactly according to this. Though it seems to work correctly, but it messes up the previously working actions.
Below are a minimal example of my base and child controllers.
[RoutePrefix("api/{controller}")]
public class MyController<TEntity, TDto>: ApiController{
[HttpGet]
public IEnumerable<TDto> All(){
...
}
[HttpGet, Route("lookup")]
public virtual IEnumerable<TDto> LookupData(){
...
}
}
[RoutePrefix("api/entity")]
public class EntityController : MyController<Entity, DTO>
{
}
After implementing the route attribute inheritance, the api/entity/lookup action works but in case of api/entity (for All), ActionSelector returns 2 actions, both All, and LookupData, thus causing error.
I am not sure why it is selecting an action with Route attribute even in case of a regular route. What I should do differently? Or is there any robust way to write a ActionSelector for this problem?
Try adding empty [Route] to All method:
[HttpGet]
[Route]
public IEnumerable<TDto> All(){
...
}
I'm using Web API v2 and I have a handful of models that I need to do CRUD operations for. For example, I have an Allergy model and a Prescription model. In the application itself I have viewmodels which can turned into their appropriate models, but for simplicity's sake let's just say I take the model straight in the Web API controller.
So something like this:
public class PrescriptionsController
{
public HttpResponseMessage Put(Prescription model)
{
// saved to the DB
}
... (other CRUD operations)
}
I also have the same for the Allergy model:
public class AllergiesController
{
public HttpResponseMessage Put(Allergy model)
{
// saved to the DB
}
... (other CRUD operations)
}
Both models have different properties but are handled exactly the same way - in fact I have about 3 other models which are handled exactly the same way for each CRUD operation. I hate to do have 5 different endpoints that are basically copied and pasted code.
So my question is this:
Can I make a generic controller to handle all of these models? Something like MyCommonController<T>? (but with a better name of course!) Can the Web API handle the routing in that scenario? Is that even a good idea?
In the end I didn't try a generic controller. It seemed like it might be possible via jumping through some hoops with routing.
However, the fact that routing modifications to get this to work were so complicated it kind of negated the benefit I would get. I wanted to keep things simple. So I just created a generic base class instead:
class MyBaseController<TModel> : ApiController
{
public TModel Get(int id) { ... }
}
and had each type inherit from it:
class PrescriptionsController : MyBaseController<Prescription> { }
And that worked like charm, didn't have to mess with routing or anything. It makes it clear what's happening and is pretty maintainable.
I got a ASP.NET MVC controller like this
[Authorize]
public class ObjectController : Controller
{
public ObjectController(IDataService dataService)
{
DataService = dataService;
}
public IDataService DataService { get;set;}
}
The Authorize attribute is defined as "Inherited=true" in the framework. So when i make the next controller:
public class DemoObjectController : ObjectController
{
public DemoObjectController(IDataService dataService)
: base (dataService)
{
DataService = new DemoDataService(DataService);
}
}
It gets the authorize attribute, but i don't want it here. I want the Demo Object controller to be available to everyone, cause it just uses fake data.
I guess I'll implement my own Authorize attribute that don't get inherited, for i can't find any way to remove the attribute from the inherited class.
Since it is marked as inherited, there isn't much you can do in this case (since you don't control the code that is checking for the attribute via reflection). Implementing your own attribute seems the most practical option.
With MVC you can also often achieve the same functionality with overrides (the On* methods), which might be worth looking into.
If a base type requires authorization then all child types ought to require authorization as well. I think that you ought to inherit from a different type but a workaround would be to declare your own AthorizeAttribute that works for this particular instance.