Creating a simple controller alias - yii

I'm not sure I am using the proper terminology, so I will describe what I want to achieve.
I have a controller called ControllerA and want a "virtual" controller called ControllerB to function exactly the same as ControllerA.
Basically I just want the url site.com/ControllerB to load up the same page as site.com/ControllerA (but not redirect).
Hope my description is clear enough.

You can achieve what you want with a simple URL rule:
'controllerA/<a>' => 'controllerA/<a>',
'controllerB/<a>' => 'controllerA/<a>',
Read more about URL rules here: http://www.yiiframework.com/doc/guide/1.1/en/topics.url#user-friendly-urls

You can extend ControllerA with ControllerB and provide extended controller name. Next override getViewPath method. Attribute extendedControler give us basic controller name.
class ControllerBController extends ControllerAController
{
private $extendedControler = 'ControllerA';
public function getViewPath() {
$nI = Yii::app()->createController($this->extendedControler);
return $nI[0]->getViewPath();
}
}
Of course you can use some string modification. Like str_ireplace:
class Klient2Controller extends KlientController
{
public function getViewPath() {
//We must extract parent class views directory
$c = get_parent_class($this);
$c = str_ireplace('Controller', '', $c); //Extract only controller name
$nI = Yii::app()->createController($c);
return $nI[0]->getViewPath();
}
}

Related

ASP NET Core Define API Controller within class

I'm currently switching from .net framework to .net core 3.1.
Defining Api Controllers inside the namespace is all fine and works.
Now I have the case, that I need to declare the Api Controllers within another class, like this:
namespace Api.Controllers
{
public class MainClass : BaseClass
{
public MainClass()
{
}
[ApiController]
[Route("Test")]
public class TestController : ControllerBase
{
[HttpGet]
public int GetResult()
{
return 0;
}
}
}
}
The result is, that the Api Controller can't be found after calling the "AddControllers" method inside the "ConfigureServices" method of the startup class.
The MainClass is instantiated before the Startup class will be called.
I've tried to change the global route and defining an area like "{area=Test}/{controller=Test}", or set the ApiController attribute above the MainClass, but none of them worked.
Is there a way to get this working?
Looks like the default ControllerFeatureProvider does not treat nested controller types as controller. You can add (don't need to replace) your custom provider to change that behavior, like this:
public class NestedControllerFeatureProvider : ControllerFeatureProvider
{
protected override bool IsController(TypeInfo typeInfo)
{
if(!typeInfo.IsClass) return false;
if(typeInfo.IsAbstract) return false;
var isNestedType = typeInfo.DeclaringType != null;
var isPublic = true;
var parentType = typeInfo.DeclaringType;
while(parentType != null){
isPublic = parentType.IsPublic;
parentType = parentType.DeclaringType; ​
​ }
​ return isNestedType && isPublic; ​
}
}
Then add that provider to the ApplicationPartManager in Startup.ConfigureServices like this:
​​services
.AddMvc()
​​ .ConfigureApplicationPartManager(appPart => {
​​appPart.FeatureProviders.Add(new NestedControllerFeatureProvider());
});
If you want to replace the default ControllerFeatureProvider, just find it in the FeatureProviders and remove it. Of course then you need to ensure that your custom one should handle everything just like what done by the default logic, something like this:
​​//for IsController
return base.IsController(typeInfo) || <...your-custom-logic...>;
NOTE: You can refer to the default implementation of ControllerFeatureProvider to learn some standard logic to implement your own logic correctly. The code above is just a basic example. To me, as long as the classes inherits from ControllerBase and not abstract, they can work fine as a controller to serve requests. There would be no serious troubles except some weird conventions (e.g: class name not ending with Controller is still a controller or some standard attributes applied on the controller class are not working ...).
We should not use nested controller classes. Each controller class should be put in a separate file (as a good practice). However the point of this answer (the most interesting part that I'm pretty sure not many know about, is the use of ControllerFeatureProvider which can help you customize the features set in other scenarios). And really if you really have to stick with your design somehow, you of course have to use this solution, no other way.

Extend Aurelia Validation Rules on a per class basis

If I have a class
export class Person {
public name: string = "";
public foo: string = "";
}
ValidationRules
.ensure((p :Person) => p.name)
.required()
.withMessage("name is required")
.on(Person);
Is there any way to extend those rules on a per controller basis? For example in my App class I also want to ensure the foo property is set, however adding this as a rule here seems to override the name rule from the above code.
export class App {
public person: Person = new Person();
#observable
public message: string = "";
constructor(public vc: ValidationController, public vld: Validator) {
ValidationRules
.ensure((p: Person) => p.foo).required().withMessage("foo is required").on(this.person);
this.vc.addObject(this.person);
this.vc.validate();
}
}
Yes that's possible, but it requires a slightly different approach.
There are 2 important things to note here:
The fluent api initializer (static method ensure() on ValidationRules) always instantiates a new FluentEnsure object. It doesn't look for existing stuff - not even if you finalize on the same target. To extend a ruleset with more rules, you need to call .ensure() on the existing ruleset.
The finalizer (instance method on() on FluentEnsure) stores the rules in the .prototype.__rules__ (if it's a Function) or .__rules__ property of the target you pass in and will overwrite any existing one.
In other words when you finalize on an instance of Person, you're storing a brand new rules object on person.__rules__ which effectively hides Person.prototype.__rules__.
For the record, the static methods Rules.set(target, rules) and Rules.get(target) are wrappers around the .__rules__ property. You'll definitely want to call those and not try to access the property directly.
Now you might think something like Rules.get(Person).ensure(...).on(person) but that would also modify the original rules on Person.prototype.
So how to work with that?
Enter tags
It can get messy with fetching and combining rulesets, but here's the basic idea:
export class Person {}
ValidationRules.ensure(p => p.name).required().on(Person)
And elsewhere:
Rules.get(Person).ensure(p => p.foo).required().tag("foo");
And elsewhere:
Rules.get(Person).ensure(p => p.bar).required().tag("1234");
Then when it's time to validate:
const allRules = Rules.get(Person); // "1234" is included
const fooRules = ValidationRules.untaggedRules(allRules)
.concat(ValidationRules.taggedRules(allRules, "foo"); "1234" not included
vc.addObject(person);
vc.validate({ fooRules });
Or hand roll something
I've never actually used tags myself before and I've seen an issue or two regarding them. You could also do something similar yourself if you want more control/transparency:
export class Person {
public static scopedRules: { [scope: string]: any } = Object.create(null);
}
ValidationRules.ensure(p => p.name).required().on(Person)
And elsewhere:
Person.scopedRules.foo = Object.create(null);
ValidationRules.ensure(p => p.foo).required().on(Person.scopedRules.foo)
Then when it's time to validate:
const rules = Rules.get(Person).concat(Rules.get(Person.scopedRules.foo));
vc.addObject(person);
vc.validate({ rules });
Of course this is just a "simplest possible thing" example. In a real world scenario you'd probably have your rule storage/retrieval/merging etc tucked away somewhere.

How to Redirect from one view to another view with data - Yii2

i want to pass 1 variables from a view to another view with post method. use this redirect code but its not working
Yii::$app->getResponse()->redirect('home','id'=>$id)->send();
try this
Yii::$app->getResponse()->redirect(['home','id' => $id])->send();
For the sake of completeness in a controller context you may use:
class MyController extends \yii\web\Controller
{
public function actionIndex()
{
return $this->redirect(['home', 'id' => 123]);
}
}
Where the array parameter of redirect() is equal to yii\helpers\Url::to().

Kohana - Best way to pass an ORM object between controllers?

I have Model_Group that extends ORM.
I have Controller_Group that gets a new ORM:
public function before()
{
global $orm_group;
$orm_group = ORM::factory('Group');
}
...and it has various methods that use it to get different subsets of data, such as...
public function action_get_by_type()
{
global $orm_group;
$type = $this->request->param('type');
$result = $orm_group->where('type', '=', $type)->find_all();
}
Then I have another controller (in a separate module) that I want to use to manipulate the object and call the relevant view. Let's call it Controller_Pages.
$orm_object = // Get the $result from Controller_Group somehow!
$this->template->content = View::factory( 'page1' )
->set('orm_object', $orm_object)
What is the best way to pass the ORM object from Controller_Group to Controller_Pages? Is this a good idea? If not, why not, and what better way is there of doing it?
The reason for separating them out into different controllers is because I want to be able to re-use the methods in Controller_Group from other modules. Each module may want to deal with the object in a different way.
This is the way I would do it, but first I would like to note that you shouldn't use global in this context.
If you want to set your ORM model in the before function, just make a variable in your controller and add it like this.
public function before()
{
$this->orm_group = ORM::factory('type');
}
In your Model your should also add the functions to access data and keep the controllers as small as possible. You ORM model could look something like this.
public class Model_Group extends ORM {
//All your other code
public function get_by_type($type)
{
return $this->where('type', '=', $type)->find_all();
}
}
Than in your controllers you can do something like this.
public function action_index()
{
$type = $this->request->param('type');
$result = $this->orm_group->get_by_type($type);
}
I hope this helps.
I always create an helper class for stuff like this
Class Grouphelper{
public static function getGroupByType($type){
return ORM::factory('Group')->where('type','=',$type)->find_all();
}
}
Now you're been able to get the groups by type where you want:
Grouphelper::getGroupByType($type);

Pattern for passing common data to _layout.cshtml in MVC4.5

I am trying to come up with the best pattern for passing data to my _layout.cshtml page.
I am toying with creating a common base class from which all view specific models derive. This base class would be recognized by my _layout.cshtml and used to fill in details about the user and load proper images in the header, etc. For example, here is a snippet of it.
public abstract class ViewModelBase
{
public string Username { get; set; }
public string Version { get; set; }
}
At the top of my _layout.cshtml I have...
#model MyProject.Web.Controllers.ViewModelBase
I need a common area to hydrate the information required by the model, and am planning to use the following pattern...
Each action method creates and hydrates a model derived from
ViewModelBase.
The action completes.
I create a ActionFilterAttribute and override OnActionExecuted to cast the
current Result to ViewModelBase.
If the conversion is successful, then I populate the ViewModelBase details with the relevant data.
Here are my questions...
Is the use of a ActionFilterAttribute (OnActionExecuted) a good pattern for what I am trying to do?
I am not able to see how to get the Result created in the action from the HttpActionExecutedContext. How is this done?
I follow the same approach and use a base ViewModel class which all my other viewModels inherit from.
Then, I have a base controller that all controller inherit from. In there, I have one method that takes care of initializing the view model:
protected T CreateViewModel<T>() where T : ViewModel.BaseViewModel, new()
{
var viewModelT = new T {
HeaderTitle = "Welcome to my domain",
VisitorUsername = this.VisitorUsername,
IsCurrentVisitorAuthenticated = this.IsCurrentVisitorAuthenticated,
//...
};
return viewModelT;
}
Then on each controller, when I want to create the view model, I simply call the base controller's method:
var vm = base.CreateViewModel<MyPageCustomViewModel>();