I have 10 controllers that use the same block of code but I can't figure out how to write the code once and use it everywhere.
I have to define an object called:
requiredStructuralSupportParameters
, then set 3 fields in the object.
This is one of the controller methods that uses it:
public class StructureController : Controller
{
public IActionResult Index()
{
var requiredStructuralSupportParameters = new Structure.RequiredInfo()
{
Steel = "1500y",
Concrete = "5500l",
Rebar = "95000y"
};
var response = callToAPI(requiredStructuralSupportParameters);
return response.Results;
}
}
I have tried taking that code out and putting it at the top of the controller class and making it public, but then my controllers can't see it and I get nullreferenceexception errors.
So it only works when I put it directly in the controller methods.
Is there a way to make this so that all controllers can re-use the same block of code?
public class StructureController : Controller
{
protected YourType _requiredStructuralSupportParameters;
public StructureController()
{
this._requiredStructuralSupportParameters = new Structure.RequiredInfo()
{
Steel = "1500y",
Concrete = "5500l",
Rebar = "95000y"
};
}
}
then have your other controllers inherit your StructureController:
public SomeController : StructureController{
public IActionResult Index() {
var response = callToAPI(this._requiredStructuralSupportParameters);
return response.Results;
}
}
haven't tested it but i hope you get an idea
Related
One of the most popular books on ASP.NET Core is "Pro ASP.NET Core 3" by Adam Freeman.
In chapters 7-11, he builds an example application, SportsStore.
The Index method of the HomeController shows a list of products:
Here's the Index method:
public ViewResult Index(int productPage = 1)
=> View(new ProductsListViewModel {
Products = repository.Products
.OrderBy(p => p.ProductID)
.Skip((productPage - 1) * PageSize)
.Take(PageSize),
PagingInfo = new PagingInfo {
CurrentPage = productPage,
ItemsPerPage = PageSize,
TotalItems = repository.Products.Count()
}
});
The view file that corresponds to this method is Views\Home\Index.cshtml. This file has the following line at the top:
#model ProductsListViewModel
So the view is expecting an object of type ProductsListViewModel. However, in Visual Studio, IntelliSense shows View as expecting an argument of type object:
I'm surprised that View here isn't shown to expect an object of type ProductsListViewModel. Since it's setup to accept an argument of type object, we can actually pass in some nonsensical value:
public ViewResult Index(string category, int productPage = 1) =>
View(10);
and the project will still compile!
Is there a way to set things up so that View actually only accepts the model type specified in the view file? I.e. in this case, set things up so that View only accepts ProductsListViewModel?
Thanks!
By using generic, I did the following.
//This is the Base class for every model must inherit.
public class EntityModel
{
public int ID { get; set; }
}
//Model / view model must inherit from EntityModel
public class EmployeeModel : EntityModel
{
public string Name { get; set; }
}
Create a new ViewController which accepts only the EntityModel or its derived class.
Mark the View and its overloaded method obsolete so that only the entity Model calls as a parameter can only be used. Also if anyone uses it show restrict them for using it. Therefore throw error.
public class ViewController<T> : Controller where T: EntityModel
{
[Obsolete]
public override ViewResult View()
{
return base.View();
}
[Obsolete]
public override ViewResult View(object model)
{
throw new Exception("Use view method which accepts EntityModel");
}
[Obsolete]
public override ViewResult View(string viewName)
{
throw new Exception("Use view method which accepts EntityModel");
}
[Obsolete]
public override ViewResult View(string viewName, object model)
{
throw new Exception("Use view method which accepts EntityModel");
}
public new ViewResult View(T model)
{
return base.View(model);
}
}
Use the newly created ViewController in your Home Controller.
public class HomeController : ViewController<EmployeeModel>
{
public IActionResult Index()
{
EmployeeModel emp = new EmployeeModel();
emp.ID = 1;
emp.Name = "Satish Pai";
return View(emp);
}
}
I don't think there is a way to catch any wrong view models got passed into views at compile time!
The problem
The view is strongly typed with the view model you declare on the top so it knows what model is coming in, but the controller doesn't know which view you want it to return to...
By default, yes the controller is going to return to a view that has the same name as the method, but you can change that default, and you can even pass the name of the view as string parameter to one of the View() overloads:
public IActionResult Index(string category, int page = 1)
{
...
return View("OutOfStock", vm);
}
Now Visual Studio doesn't know which view model you want the controller to build and pass to the view. In fact, Visual Studio doesn't even know what view I want to return. Even after I put "OutOfStock" as the view name, Visual Studio doesn't know whether the view even exists or not...
Using Generic
#Satish's solution is indeed interesting but it assumes you are only working with 1 single view model for a single controller, which normally isn't the case. Usually you will have different view models for different actions.
If Generic were the way to go, I would suggest to put it on the action, rather on the controller:
public abstract class BaseController : Controller
{
public ViewResult View<T>(T viewModel) where T : new()
{
return View(viewModel);
}
}
Then you can use it like this in the controller:
public class ProductController : BaseController
{
public IActionResult Index(string category, int page = 1)
{
var vm = new ProductListViewModel
{
...
};
return View<ProductListViewModel>(vm);
// This would give you the compile time error!
// return View<ProductListViewModel>(10);
}
}
But why? What's the point of doing this? You, as the developer, have to know ProductListViewModel is the right view model to pass anyway. Putting something like this in place would be only helpful if there is a junior or new hire who's working on your code and doesn't bother to check the view model the returned view is asking for?
Maybe?
Now I know a tool like Resharper might be able to help and catch the mismatch at compile time.
Also writing unit tests on what the methods in the controller return might be helpful?
I am currently using the Request.Scheme and Request.Host to composite Uri object to get AbsoluteUri for my .net core MVC application.
Uri location = new Uri($"{Request.Scheme}://{Request.Host}");
string applicationRootURL = location.AbsoluteUri;
But this only works in a non-static method.
As I need to re-use this method in another controller, I am thinking to make this action method static. If I do that, the compiler will complaint about the Request.Scheme and Request.Host.
I am wondering what's other options I have to achieve this?
Thank you.
UPDATE:
This is what I have for ControllerA with ActionMethodA
public class ControllerA
{
public bool ActionMethodA()
{
Uri location = new Uri($"{Request.Scheme}://{Request.Host}");
string applicationRootURL = location.AbsoluteUri;
return false;
}
}
And in another ControllerB, I want to ActionMethodB to invoke ActionMethodA from ControllerA:
public class ControllerB
{
public void ActionMethodB()
{
var result = ActionMethodA();
}
}
Is creating an Extension Method to the ControllerA is the most proper way to handle this kind of scenario?
Thank you.
You can also define an extension method directly for the HttpRequest class and use the BuildAbsolute method of the UriHelper class to build the uri.
public static class HttpRequestExtensions
{
public static string GetURI(this HttpRequest request)
{
return UriHelper.BuildAbsolute(request.Scheme, request.Host);
}
}
And use it:
public IActionResult ContollerMethod()
{
var uri = Request.GetURI();
// your code
}
You can write an extension method to a controller or HttpContext object. In the following example I have added an extension method to the controller.
public static class ControllerExtensions
{
public static string GetURI(this Controller controller)
{
Uri location = new Uri($"{ controller.Request.Scheme}://{controller.Request.Host}");
string applicationRootURL = location.AbsoluteUri;
return applicationRootURL;
}
}
Once the extension method is written you can call it in the following manner.
public IActionResult Index()
{
var url = this.GetURI();
return View();
}
Make sure to import namespace of an extension method in your calling code
I would to override constroller constrcuter's like this :
class XControler extends AppController {
public $attr = null;
public __construct(){
$this->attr = new YController();
}
}
But when I do that I take error ! can you explain me why and how I do that with out using requestAction just OOP !
thanks
Controllers are responsible for dealing with end user requests. Each controller action should have a view, and normally you would not want to access the methods from YController inside XController.
What you want to achieve can be done this way:
XController.php
App::uses('YController', 'Controller');
class XController extends AppController {
public $attr;
public $uses = array('Person');
public function __construct($request = null, $response = null) {
$this->attr = new YController();
parent::__construct($request, $response);
}
public function method1() {
// you can now call methods from YController:
$this->attr->Ymethod1();
}
}
YController.php
class YController extends AppController {
public function Ymethod1() {
// ....
}
}
However, the business logic should be inside Models or Components. This is the proper way to share methods between more controllers.
So your XController should look like:
class XController extends AppController {
public $uses = array('Model1');
public function action1() {
$this->Model1->method1();
// ....
}
}
I know that this is one of the most encountered error but I am really struggling to get around it.
i have a property on my Controller:
private readonly ISelectListFactory _selectFactory;
and a method that called to populate the viewbag
private void PopulateLists()
{
var continent = _selectFactory.GetItems();
}
and the interface
public interface ISelectListFactory
{
IEnumerable<SelectListItem> GetItems();
}
and in the controller constructor I have the following:
public LocationController(ISelectListFactory selectFactory)
{
_selectFactory = selectFactory;
}
but I am getting this error Object reference not set to an instance of an object and not sure how to overcome it.
Regards
Make sure you have instantiated this _selectFactory variable somewhere. Like for example:
_selectFactory = new SomeConcreteSelectListFactory();
or if you are using dependency injection you might configure your DI framework to inject it into the constructor:
public class HomeController: Controller
{
private readonly ISelectListFactory _selectFactory;
public HomeController(ISelectListFactory selectFactory)
{
_selectFactory = selectFactory;
}
... some controller actions where you could use the _selectFactory field
}
i have a class (in project by mvc4 razor on .net 4.5) and want to handle a Redirecting method on it and do not want inherit from controller class.
how can i handle this?it returns ActionResult to redirecting user in some cases like log outing
my main class:
public class SecuritySrv
{
public ActionResult Redirect()
{
return RedirectToAction("Logout", "Account", new { area = "" });
}
}
and i want to use it in some controllers like below:
public ActionResult AccountHome()
{
SecuritySrv SecurityService =new SecuritySrv()
if(.....)
return SecurityService.Redirect();
return view();
}
You can use this code anywhere, and you don't need an UrlHelper or access to the context, so you don't need to inherit the Controller class.
RouteValueDictionary rvd = new RouteValueDictionary
{
{"controller", "Profile"},
{"action", "Users"},
{"area", ""}
};
return new RedirectToRouteResult(rvd);
The RedirectToAction method of controller is just a helper for creating RedirectToRouteResult, you can create it by yourself in your class:
public class SecuritySrv
{
public ActionResult Redirect()
{
RouteValueDictionary routeValues = new RouteValueDictionary();
routeValues["action"] = "Logout";
routeValues["controller"] = "Account";
routeValues["area"] = "";
return new RedirectToRouteResult(routeValues);
}
}
And call this method from your controller in the way you wanted to.