I'm new to asp.net mvc (5) and I am facing an issue on my website.
Basically, all aspnet_users are linked to my specific user table via the guid.
I have a BaseController class with a UnitOfWork and a ViewModelBase :
public class BaseController : Controller
{
protected UnitOfWork UnitOfWork { get; private set; }
public ViewModelBase ViewModel { get; set; }
}
(Extract of) the ViewModelBase class, containing the information needed in the layout page :
public class ViewModelBase
{
public User User { get; set; }
public bool IsAuthentified { get; set; }
public bool Loaded { get; set; }
}
And the layout which uses it :
#model Project.ViewModels.ViewModelBase
<html>
<body>
Some very cool layout stuff related to the User in the ViewModelBase
</body>
</html>
Here is an example of use with the HomeController :
public class HomeController : BaseController
{
private new HomeViewModel ViewModel
{
get { return (HomeViewModel)base.ViewModel; }
}
public HomeController()
{
ViewModel = new HomeViewModel();
}
public ActionResult Index()
{
// I need to get the MembershipUser here to retrieve the related User and set it in the ViewModel
return View(ViewModel);
}
}
The HomeViewModel :
public class HomeViewModel : ViewModelBase
{
public string HomeSpecificProperty { get; set; }
}
And the view :
#model Project.ViewModels.HomeViewModel
#{
ViewBag.Title = "Home";
Layout = "~/Views/Shared/Layout.cshtml";
}
Welcome #Model.User.UserName !
<br />
#Model.HomeSpecificProperty
And here is my problem. The thing is all the pages have a viewmodel inherited from ViewModelBase, since it is used in the layout, but I don't know where nor how to set the User property in it.
Since Membership is used to retrieve the User it has to be in an Action, I can't do this in the ViewModelBase constructor.
Thus I added this code in the BaseController, which sets the ViewModelBase properties on the first get :
private ViewModelBase viewModel;
protected ViewModelBase ViewModel
{
get
{
if (!viewModel.Loaded)
LoadBaseContext();
return viewModel;
}
set { viewModel = value; }
}
private void LoadBaseContext()
{
viewModel.IsAuthentified = HttpContext.User.Identity.IsAuthenticated;
if (viewModel.IsAuthentified)
{
MembershipUser user = Membership.GetUser();
viewModel.Player = UnitOfWork.UserRepo.Get((Guid)user.ProviderUserKey);
}
viewModel.Loaded = true;
}
Might not be very beautiful, but works. However, since there is some database acesses in it (notably to get the User), I thought I should put the LoadBaseContext function async.
I tried, and since all actions use ViewModelBase I put every action async too. But then #Html.ActionLink doesn't work anymore since the action called is async.
Finally, my question is : Is this ViewModelBase and User property the right way to do ? If it is, should the LoadBaseContext be async ? Then, how to make it work with the actions ?
Thanks a lot.
UPDATE
Extract of the layout :
#if (!Model.IsAuthentified)
{
#Html.Action("Index", "Authentification", null) // display login partial view
}
else
{
#Html.Action("Index", "UserInfos", null) // display userinfos partial view
}
Authentification controller Index's action :
public ActionResult Index()
{
ViewModel = new AuthentificationViewModel();
// loads the ViewModelBase properties here
return PartialView("~/Views/Shared/Partials/Login.cshtml", ViewModel);
}
If you have something that you always want in all of your views, you could either ensure that every view takes a view model that includes or extends that core set of data, or you could simply put the data in the ViewBag.
To do the latter, you could override OnActionExecuting in your base controller, and set the ViewBag contents there.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
this.ViewBag["IsAuthenticated"] = this.Request.IsAuthenticated;
// ...
}
If you want async behaviour, then you might be able to adapt this post to your needs.
To do this without overriding OnActionExecuting, you could put an async method in your controller base class that sets the data in the ViewBag:
protected async virtual Task PopulateViewBag()
{
this.ViewBag["foo"] = await MyAsyncMethod();
// ...
}
... and then simply call this from each and every one of your controller actions:
public async Task<ActionResult> MyAction()
{
await this.PopulateViewBag();
// ... more code
}
It would be a bit tedious, though.
One final word: depending on how many users you expect to have etc. it may be easier to get the user information once, and then cache it (e.g. in the Session), rather than repeatedly get it during every request. Presumably most of the user info isn't going to change between requests...
Related
I've been reseraching dynamic CSS and the best solution I can find seems to be here on Stack Overflow, It's all looking good so far even though I made some changes but the one thing I can seem to figure out is what the View should be named and where I should place it.
I'm assuming that becasue it's a partial view it should be placed in Shared, but wanted to check that assumption. The controller the partial view code is located in is a Base Controller that all the other contollers then inherit (not sure if that makes a difference).
public class BaseController : Controller
{
public ActionResult CssDynamic()
{
Layout page = new Layout();
var dummyCorpId = 80;
page.Css = GetCss(dummyCorpId);
var model = page;
return new CssViewResult(model);
}
}
public class CssViewResult : PartialViewResult
{
private readonly object model;
public CssViewResult(object model)
{
this.model = model;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ContentType = "text/css";
base.ExecuteResult(context);
}
}
I've then added the action to the _Layout page, just not sure which View name to use or where to place it. Any help appriciated :)
I have such controller system in my project:
BaseController : Controller
AnyController : Controller
Code in my base Controller:
protected ViewResult View(string viewName, BaseViewModel model)
{
model.PromoBannerContent = _service.GetPromoBannerContent();
return base.View(viewName, model);
}
BaseViewModel - it's the layout model. And example of simple Action:
public virtual ActionResult UpdateAccount()
{
AccountViewModel account = _accountService.GetUser(Luxedecor.User.UserId).ToAccountViewModel();
return View(MVC.Account.Views.UpdateAccount, new
UpdateAccountViewModel()
{
AccountJson = JsonConvert.SerializeObject(account),
States = _accountService.GetStates(account.Country)
});
}
Where UpdateAccountViewModel : BaseViewModel.So my layout page is looking like that:
#model Luxedecor.ViewModel.BaseViewModel
#if (Model.PromoBannerContent != null)
{
...//Some html code
}
It's working fine, but I need to render the html promo banner not at all controller pages. I mean that I have for example AccountController: BaseController and I don't need this banner the AccountController Views. So I can create boolean property in my BaseViewModel and pass it from each Action in AccountController and other Contollers... But I wonder is exist more elegant solution for this issue? Could anyone experienced help me?
So, this is solution, what I've used for that:
public class RenderPromoBanner : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewBag.EnableBanner = true;
}
}
And then I've just used one if statement on the layout.
If you need to decide whether you need the banner or not on a per-Action basis (e.g. some actions in a controller need a banner whereas others in the same controller don't), the Action needs to communicate whether the banner is needed. Using a boolean is a viable option for this. Also, you could move the call to _service.GetPromoBanner into the Action and set the model property there if the banner is needed.
If all Actions in a controller need a banner or not, you could also create another subclass for the controllers that need a banner. This new subclass inherits from BaseController and contains the code to get the banner content. If a controller needs a banner, it derives from BannerController else from BaseController:
class BaseController : Controller
{
protected virtual ViewResult View(string viewName, BaseViewModel model)
{
return base.View(viewName, model);
}
}
class BannerController : BaseController
{
protected virtual ViewResult View(string viewName, BaseViewModel model)
{
model.PromoBannerContent = _service.GetPromoBannerContent();
return base.View(viewName, model);
}
}
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 have two Viewmodels, a view and two partial views and a controller method for submit.
Lets name them, BigViewModel, SmallViewModel, Page.cshtml, _Options.cshtml, _Submit.cshtml and Submit(BigViewModel) method in controller.
SmallViewModel is included in BigViewModel as one of the properties.
like,
public class BigViewModel
{
public bool Confirm { get; set; }
public SmallViewModel s { get; set; }
}
Now, Page.cshtml has a division where _Options.cshtml resides where user can input few options like sorting, searching etc. This _Options.cshtml uses a viewmodel called SmallViewModel. When you click on Submit button which is on _Options.cshtml partial view, it opens a dialog box that is _Submit.cshtml which asks for confirmation and submit button. This confirmation information is stored in BigViewModel. Now, when I hot submit it goes to Submit(BigViewModel) method in the controller.
My problem is how to include SmallViewModel data into BigViewModel from _Submit.cshtml partial view page. Because, when I go to the Submit(BigViewModel) method, I only see Confirm - True/False. But smallViewModel is null.
Hope, question is clear enough. I didn't know how else to ask. Please help.
You can simply create an interface that all models with options will derive from. Here is a sample :
public class BigViewModel : IModelOptions
{
public bool Confirm { get; set; }
public SmallViewModel SmallView { get; set; }
}
public class SmallViewModel
{
public string Stuff{ get; set; }
}
public interface IModelOptions
{
SmallViewModel SmallView { get; set; }
}
this way if you ever need more models with options you can simple do "NewModel : IModelOptions". Now your _options view would look something like
#model MvcApplication1.Models.IModelOptions
#Html.TextAreaFor(x => x.SmallView.Stuff)
Note that now your partial view does not need to be aware of what model is being passed in. So long as it derives from IModelOptions then it knows it will have a SmallView. Our _submit would be something like
Are you sure? If so I will use fancy javascript to allow you to submit!
<input type="submit"/>
and then we tie that all together in our main view (Index , Page, what have you)
#model MvcApplication1.Models.BigViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.Confirm)
{
Html.RenderPartial("_Options");
Html.RenderPartial("_Submit");
}
}
Note that here we take in a BigViewModel. That should work fine as our BigViewModel houses Options and derives from IModelOptions.
Our controller would simply be
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(BigViewModel model)
{
var smallVIewModelInfo = model.SmallView.Stuff;
var bigViewModelConfirm = model.Confirm;
return View();
}
}
and now you should be getting data from both small and big view model.
I have been thinking about this for the past few days and was wondering what everyone else's take is on it. I want to implement some content that is based on what role the user belongs to. Our applications uses HTTP headers to receive role information. In MVC should I have the controller handle the role detection, and then pass a boolean value into the viewmodel.
Viewmodel:
public class ProductViewModel
{
public Product MyProduct { get; set; }
public bool IsAdmin { get; set; }
public bool IsSuperAdmin { get; set; }
}
Controller:
public class ProductController : Controller
{
public ActionResult Info()
{
//get product information here
ProductViewModel pvm = new pvm();
pvm.Product = theProduct;
pvm.IsAdmin = checkAdminAccess(); //This would return a bool if they had access
pvm.IsSuperAdmin = checkSuperAdminAccess();//This would return a bool if they had access
return View(pvm);
}
}
View:
#if(Model.IsAdmin == true || Model.IsSuperAdmin == true)
{
//Render Content for admin (Either directly or via Partial View)
}
#if(Model.IsSuperAdmin == true)
{
//Render Superadmin content (Either directly or via Partial View)
}
//Implement other Product Stuff Here
Or would it be better to create a partial view and handle the logic in the view. This seems like there is less work involved.
#if (Request.Headers.AllKeys.Contains("role")){
ViewBag.Role = Request.Headers["role"].ToString();
}
...
#if(ViewBag.Role == "Admin" || ViewBag.Role == "SuperAdmin")
{
#Html.Partial("AdminPartial")//Or Render Code Directly?
if(ViewBag.Role == "SuperAdmin")
{
#Html.Partial("SuperAdminPartial")//Or Render Code Directly?
}
}
//Implement other Product Stuff Here
Is there any added benefits to doing it one way or another, should I be rendering the content directly or using a partial view or does it not matter? Or am I missing some other way that this should be done?
You can use the AuthorizeAttribute to restrict access/use of controller actions:
[Authorize(Roles = "Admin, Customer")]
public class ProductController : Controller
{
public ActionResult Info()
{
//get product information here
ProductViewModel pvm = new pvm();
return View(pvm);
}
[Authorize(Roles = "Admin")]
public ActionResult EditInfo()
{
//get product information here
EditProductViewModel epvm = new epvm();
return View(epvm);
}
}
This is the most basic way to restrict access by callers to an action method. However, with a large application with many roles/controllers/actions, annotating and keeping track of all attributes can become difficult.
There is product called Fluent Security that allows security to be specified in a central place, and gets rid of the issue mentioned previously.
There is also the issue of the single responsibility principle, look at Darin's answer to this question - he explains it quite well.