I've created an ASP.NET MVC controller which responds with data from a data repository. The repository is pretty simple (underlying EF6 backend) and the data is specific to a user. So my actions typically look like this:
public class MyController : Controller
{
private IRepository _repository = new MyDataContextRepository();
[HttpPost]
[Authorize]
public ActionResult GetMyData()
{
var result = _repository.GetData(Membership.GetUser().ProviderUserKey);
return Json(result);
}
}
But because I'll be using the user'd ID in nearly all the calls, I'd like to initialize the repository with the current user's ID instead, like so.
public class MyController : Controller
{
private IRepository _repository = new MyDataContextRepository(Membership.GetUser().ProviderUserKey);
[HttpPost]
[Authorize]
public ActionResult GetMyData()
{
var result = _repository.GetData();
return Json(result);
}
}
The problem here is that the constructor is run before the user's officially logged in, so the GetUser() looks for the username "" (user not authorized yet).
Is it possible to initialize my data repository once after a user has been authenticated? Or can I only identify the user during the action method's call?
Standard practice would say that you should pass the user ID to the repository methods as a parameter, rather than basing the whole repository upon it.
But if you want to do it how you are, you can wrap the _repository in a property and create it the first time it is called. A simple way to do this is to use the Lazy<T> class. In this way the constructor will only be called the first time the repository is actually used and the User should be available then:
public class MyController : Controller
{
private Lazy<IRepository> _repository = new Lazy<IRepository>(
() => new MyDataContextRepository(Membership.GetUser().ProviderUserKey));
private IRepository Repository
{
get { return _repository.Value; }
}
[HttpPost]
[Authorize]
public ActionResult GetMyData()
{
var result = Repository.GetData(); // the repository constructor will get called here
return Json(result);
}
}
Related
I have a basic controller that extends from Controller, the class is working fine, but I figured that I am using a lot of times the code to get the current User from the database. So I figured I should make a constructor and move the code that I use in every function there.
Basically, what I wanted to do is have the parameters ready for any of the methods in my controller.
So, this is what I have right now (and it is working fine):
public class UsersController : Controller
{
private DBContext db = new DBContext();
public ActionResult Info()
{
User user = db.Users.Where(m => m.username.Equals(User.Identity.Name)).FirstOrDefault();
return View(user);
}
public ActionResult Edit(int? id){
User user = db.Users.Where(m => m.username.Equals(User.Identity.Name)).FirstOrDefault();
if(user.id == id){
return View(user);
}
}
}
But my idea was to create something like this:
public class UsersController : Controller
{
private DBContext db = new DBContext();
private User _user;
public UsersController()
{
_user = db.Users.Where(m => m.username.Equals(User.Identity.Name)).FirstOrDefault();
}
public ActionResult Info()
{
return View(_user);
}
public ActionResult Edit(int? id){
if(_user.id == id){
return View(_user);
}
}
}
When I made these changes I get the following error:
Server Error in '/' Application.
Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
I tried debugging and I found out that the problem is that my User is null when the constructor is called, so I am guessing, some other languages can call the parent constructor before adding or after adding their own customization, for example something like this:
public function __Construct($x){
$this->x = $x
parent::__construct();
}
or
public function __Construct($x){
parent::__construct();
$this->x = $x
}
I tried to do the same in my program, using base, but nothing seems to work and it always leads me to an error of some other nature.
I am not even sure that this is the right way to do it, because all I need is to have my User (Identity) created in the constructor
Sounds like the user isn't found, possibly because the user identity isn't populated on the thread's principal when the constructor for the controller is called.
My suggestion would be to avoid pulling the user data in the constructor and instead grab it when you need it. To avoid duplicating code, you can write a protected or private method (not an action method) to get it:
public class UsersController : Controller
{
private DBContext db = new DBContext();
private User GetCurrentUser()
{
return db.Users.Where(m => m.username.Equals(User.Identity.Name)).FirstOrDefault();
}
public ActionResult Info()
{
var user = GetCurrentUser();
return View(user);
}
public ActionResult Edit(int? id){
var user = GetCurrentUser();
if(user.id == id){
return View(user);
}
}
}
As I mentioned in my question comment, inheritance is a poor choice here. Instead what you're attempting to do is give non-specific data to a View. A better choice is to use an ActionFilter.
We need a class to store User Information for the view to consume:
public class UserInfo
{
public bool HasUser { get; set; }
public User User { get; set; }
}
We need a place to store the data that is non-specific to views. I prefer using ViewData (because this route provides strongly typed data and an easy way to debug this storage location):
public static class ViewDataExtensions
{
private const string UserInfoKey ="_UserInfo";
public static void GetUserInfo(this ViewData viewData)
{
return viewData.ContainsKey(UserInfoKey)
? viewData[UserInfoKey] as UserInfo
: null;
}
public static UserInfo SetUserInfo(this ViewData viewData, UserInfo userInfo)
{
viewData[UserInfoKey];
}
}
Next we need a way to populate that information when needed
public class AddUserToViewDataFilterAttribute : ActionFilterAttribute
{
private DBContext db = new DBContext();
public void OnActionExecuting(ActionExecutingContext context)
{
var user = context.Controller.User;
var userInfo = new UserInfo
{
HasUser = !string.IsNullOrEmpty(User.Identity?.Name),
User = !string.IsNullOrEmpty(User.Identity?.Name)
? db.Users
.Where(m => m.username.Equals(User.Identity.Name)).FirstOrDefault()
: null;
};
context.ControllerContext.ViewData.SetUserInfo(userInfo);
}
}
Populate it when needed:
public class MyController
{
public ActionResult DoesNotNeedUserInfo()
{
}
[AddUserToViewDataFilter]
public ActionResult NeedsUserInfo()
{
}
}
In the view:
#model <whatever>
#if (ViewData.GetUserInfo().HasUser) {
<div>#ViewData.GetUserInfo().User.Name</div>
}
In asp.net core I would like to set up my API controller to do the following:
by default return View(model);
/api/id.json to return model; as json
/api/id.xml to return model; as xml
The second two can be achieved by using the [FormatFilter] see here
[FormatFilter]
public class ProductsController
{
[Route("[controller]/[action]/{id}.{format?}")]
public Product GetById(int id)
However this requires the method to return an object and not a View(object). Is there anyway to cleanly support also returning Views?
You cannot do both in the same action. However, you can factor out the common functionality into a private method and then implement two actions with minimal code duplication:
[Route("[controller]")]
[FormatFilter]
public class ProductsController : Controller
{
private Product GetByIdCore(int id)
{
// common code here, return product
}
[HttpGet("[action]/{id}")]
[ActionName("GetById")]
public IActionResult GetByIdView(int id) => View(GetByIdCore(id));
[HttpGet("[action]/{id}.{format}")]
public Product GetById(int id) => GetByIdCore(id);
}
It's necessary to use different action names here, because the method signatures cannot differ merely on return type. However, the [ActionName] attribute can be used as above to make them appear to have the same name for the purposes of URL generation and such.
You can actually achieve this just using the one action. Here's an example of how I got it to work:
[FormatFilter]
public class ProductsController : Controller
{
[Route("[controller]/[action]/{id}.{format?}")]
public IActionResult GetById(int id, string format)
{
var yourModel = ...;
if (string.IsNullOrWhiteSpace(format))
return View(yourModel);
return Ok(yourModel);
}
By using IActionResult as the return type, you can return either a ViewResult or an OkObjectResult. You can get access to the format value by taking it as a parameter in your action, check if it's empty and then react accordingly.
I also added Controller as the base class in order to access the convenience methods for creating the relevant results (View(...) and Ok(...)).
If you're going to be using this pattern a lot, to keep your controllers as clean as possible, you could create a base class that exposed a "FormatOrView" method:
[FormatFilter]
public abstract class FormatController : Controller
{
protected ActionResult FormatOrView(object model)
{
var filter = HttpContext.RequestServices.GetRequiredService<FormatFilter>();
if (filter.GetFormat(ControllerContext) == null)
{
return View(model);
}
else
{
return new ObjectResult(model);
}
}
}
And then your controller can inherit from this and use the FormatOrView method
public class ProductsController : FormatController
{
[Route("[controller]/[action]/{id}.{format?}")]
public ActionResult GetById(int id)
{
var product = new { Id = id };
return FormatOrView(product);
}
}
Edit to list final accepted answer by GreyCloud: Here is a generic slightly simplified method you can put into a controller (or make an extension method or put into an abstract base class as above). Note the ?. in case the service is not defined for some reason.
private ActionResult<T> FormatOrView<T>(T model) {
return HttpContext.RequestServices.GetRequiredService<FormatFilter>()?.GetFormat(ControllerContext) == null
? View(model)
: new ActionResult<T>(model);
}
The FormatFilter is part of the content negotiation of your app, in AspNetCore, you have the control to handle your input or output formatters also on the ConfigureServices where you have more control, even you can add more media types there
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options .OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
options .InputFormatters.Add(new XmlDataContractSerializerInputFormatter(options ));
//more output formatters
var jsonOutputFormatter = options.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault();
if (jsonOutputFormatter != null)
{
jsonOutputFormatter.SupportedMediaTypes.Add("application/vnd.myvendormediatype");
}
}
}
But going back on the content negotiation in your controllers you can keep just one. The only thing is that you need to know the mediaType to return your View or your json content. Only be sure to pass an accept header with the content type you want. With the content type you are defining for an api or for an mvc application which is the content/format the client should expect
[HttpGet("[action]/{id}")]
public IActionResult public Product GetById(int id, [FromHeader(Name = "Accept")] string mediaType)
{
if (mediaType == "application/vnd.myvendormediatype")
{
var data = GetYourData(...)
return Json(data);
}
else return View("YourDefaultView");
}
I am using .net c# MVC controller to query database for many of my projects. Every time i create a new controller, I find myself having to rewrite some of the same function for the new controller hence, I thought about writing a basic controller to handle some of the basic task that I use in all my controller (e.g., run a query and run json).
In my controller, I reference the basic controller like this.
namespace myWebAPI.Controllers
{
public class esrcController : Controller
{
//
// GET: /esrc/
string db = "esrc-";
basicController BasicController = new basicController();
public string test()
{
return "test" + Request.ServerVariables["HTTP_REFERER"];
}
public string getCodingException()
{
return #"{""data"":" + BasicController.getDataNconvertToJSON(
"select * from z_codingexception order by nc_key",
BasicController.getEnviroment(db)) + "}";
}
}
}
in my BasicController, the getEnviroment looks at the url to determine the environment hence I need access to :
Request.ServerVariables["HTTP_REFERER"] and Request.ServerVariables["HTTP_HOST"].ToString().ToLower();
but Request is null in this controller, I only have access to request in the main controller. How do I reference httpRequest from basic controller?
Just because you instantiate a new instance of a controller, doesn't mean you'll have access to the context.
One option is to create an abstract base controller that all of your other controlers would inherhit from. You'll then have access to the specific objects like Request
WebApiConfig.cs
config.MapHttpAttributeRoutes();
Your Controller
public abstract class MyBaseController : Controller
{
protected void myMethod()
{
// you have access to Request here
}
}
public class MyController : MyBaseController
{
[HttpGet]
[Route("my/getstuff")]
public IHttpActionResult GetStuff()
{
// do stuff
base.myMethod();
return Ok();
}
}
Create an action filter and add it as an attribute to that class. Within the action filter yuo wil have access to the Request object. If you override the OnActionExecuting function, the functionality in your filter will be executed before your controller.
Create a custom filter
public class CustomAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//DO STUFF WITH YOUR REQUEST OBJECT HERE..
}
}
Add the filter as an attribute to your controller
[CustomAttribute]
public class esrcController : Controller
I am reworking one asp.net MVC Backoffice for full multi tenant support.
As I decided to use sharding for a perfect separation between tenants I need to access some Auth info (tenant Id) in every viewModel, only with that info I can create the right connection for the specified tenant.
For passing that info I have 3 rules:
I don't want to use session variables
I don't want to use ViewBag
I don't want to add a new Object to every VM for get auth info
Most of all I want a "code less" solution, the perfect scenario would be getting access of auth info inside the VM for example passing it as a attribute in the controller call.
I already override Controller OnAuthorization so that it adds the tenantId to my base controller (which is the base of all my controllers) every time it is called, that way I can always catch tenantId inside every controller, now I just need a way to pass that TenantId in a attribute to every VM , something like the following pseudo-code
[Authorize]
[TenantId AS A PARAM]
public ActionResult Index()
{
myViewModel vm = new myViewModel();
vm.method();
return this.View(vm);
}
1) Put this TenantId into HttpContext.Curent.Items
2) Write a static function that returns this TenantId from the context:
private static int GetTenantId()
{
return HttpContext.Current.Items["TenantId"];
}
3) Create a BaseViewModel
public abstract class BaseViewModel
{
public Func<int> GetTenantIdFunc{get;set;}
}
4) Using Dependency Injection container register your GetTenantId function and inject it through property injection to all your models
One option is to use a base view model and inheritance:
public abstract BaseViewModel()
{
public int TenantId { get; set; }
public void SetAuthInfo(BaseController controller)
{
this.TenantId = controller.TenantId;
}
}
public MyViewModel() : BaseViewModel
// no other changes needed to MyViewModel
...
public ActionResult Index()
{
var model = new MyViewModel();
model.SetAuthInfo(this);
return View(model);
}
To get this via an attribute (rather than model.SetAuthInfo) add an action filter and override OnResultExecuted and add it there, something like (untested) :
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class SetTenantActionAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var baseController = filterContext.Controller as BaseController;
if (baseController == null) return;
var model = filterContext.Controller.ViewData.Model as BaseViewModel;
if (model == null) return;
model.TenantId = baseController.TenantId;
}
}
then you could add this to your base controller (even less code than adding to every action)
I would like to know, if it is possible for each subsequent request, to not have the default constructor in the controller invoked?
Here's the fake scenario:
I have a controller (HomeController) with 3 Action methods. Now, each method uses a property from a Customer type. I don't want this Customer Type to always be instantiated for each action method.
Sample Code:
public class HomeController : SampleController
{
public HomeController(System systemInstance)
{
base.System = systemInstance;
}
public ActionResult Index()
{
//Do something with the CustomerType
base.ValidationResult = ValidationEngine.Validate(base.Customer.Address)
return View();
}
public ActionResult Index()
{
//Do something with the CustomerType
base.ValidationResult = ValidationEngine.Validate(base.Customer.ShoppingCart)
return View();
}
}
What I'm trying to achieve it that upon the first Invocation, I instantiate the base.System Property and then for each subsequent action method, I can just reference the instantiated type.
I dont want to store this type in the Session, or in the Cache.
Hope this makes sense :)
Thank u