MVC: access attribute value in action - asp.net-mvc-4

MVC 4
I have an action that is decorated with an action filter like this:
[ViewPermission(PermissionType.GlobalUser)]
public ActionResult General()
{
var permissionType = // trying to access the value passed to the filter ie. PermissionType.GlobalUser value
return View();
}
Is there a way to get the properties from the acation filter inside the action itself?
Thanks in advance.

So the quick answer is yes, you can do it doing something like this:
[ViewPermission(PermissionType.GlobalUser)]
public ActionResult General()
{
var type = GetType(this);
var method = type.GetMethod("General");
var attribute(typeof(ViewPermission));
var permissionType = attribute.PermissionType;
return View();
}
With that said, it is NOT a good idea. Doing reflection is slow. Very slow. You would see performance problems. If you really need to do this, then it is best to figure out a way to do it during initialization of the app, where performance is not as much of a concern.
Hope that helps.

Related

Alternate Model Binding On Controllers Post Action

EDIT: You can safely clone https://github.com/vslzl/68737969 and run the example. If you send
{
"intPropB":3
}
as POST body to: http://localhost:5000/api/Test
you'll see it binds clearly B object to A object.
#EDIT END
I'd like to implement alternate model binding using asp.net core 5 As you will see below, I have two alternative classes to bind at a single endpoint. Each request can contains only a valid A model or B model and A and B has different properties.
[HttpPost(Name = Add)]
public async Task<IActionResult> AddAsync(CancellationToken ct)
{
var aModel= new A();
var bModel= new B();
if (await TryUpdateModelAsync<A>(aModel))
{
_logger.LogDebug($"Model binded to A");
return Ok(aModel);
}
else if (await TryUpdateModelAsync<B>(bModel))
{
_logger.LogDebug($"Model binded to B");
return Ok(bModel);
}
_logger.LogDebug("Nothing binded!");
return BadRequest();
}
But this approach failed. Is there a proper way to implement this kind of solution?
By the way I'm using this to reduce complexity of my endpoints, I want to update a record partially and by doing this, each model will map the same record but with different logics.
Any suggestions will be appreciated. Thanks.
Couldn't manage to provide elegant way to do this but here is what I've done so far, It works but seems a little ugly in my opinion.
I changed the action method to this:
[HttpPost(Name = Add)]
[AllowAnonymous]
public IActionResult AddSync([FromBody] JObject body)
{
var aModel = JsonConvert.DeserializeObject<A>(body.ToString());
var bModel = JsonConvert.DeserializeObject<B>(body.ToString());
ModelState.Clear();
var aValid = TryValidateModel(aModel);
ModelState.Clear();
var bValid = TryValidateModel(bModel);
// some logic here...
return Ok(new {aModel,bModel, aValid, bValid });
}
I think It's not a good idea to interfere with ModelState this way.
But here is what I've done,
get the raw body of the request as JObject (requires Newtonsoft.Json package)
Parsed that json value to candidate objects
Check their validity via modelstate and decide which one to use.

Dynamic layout in Web App with MVC 4

I've had a MVC 4 / Entity web project dropped into my lap, and I've never used either before. I'm getting through it but the simple things are really tripping me up - Like hiding or displaying a link in my layout based on a parameter in the database.
I created a function in my HomeController that simply sets 2 bools in the ViewBag for whether or not a person is a manager or superuser. I call that function using
#Html.Action("SetupViewBag", "Home")
which sits right after the <body> tag in my layout. Here is the code for SetupViewBag:
public void SetupViewBag()
{
ViewBag.IsManager = ADAccess.IsManager(SessionManager.GetUserName());
ViewBag.IsSuper = SessionManager.SuperUser();
}
The bools are set properly and in the right order when I set up break points, but when I try to access them using the below code, I get a 'Cannot convert null to 'bool' because it is a non-nullable value type.'
#{
if((bool)#ViewBag.IsManager){
<li>#Html.ActionLink("Management", "Management", "Home",null, new { id = "managementLink" })</li>
}
}
There has to be something really simple I'm missing. Any help is greatly appreciated.
Based on your comment, #Dakine83, you should setup your ViewBag on the controller constructor method like so:
public class YourController : BaseController {
public YourController(){
}
}
The reason for that is because the Layout page is already rendered the time your action method has been called. The reason you have a null ViewBag.IsManager.
UPDATE: Use a base controller
public class BaseController : Controller {
public BaseController() {
ViewBag.IsManager = ADAccess.IsManager(SessionManager.GetUserName());
}
}
i hope this might work for you,please try it once
#Html.ActionLink("Management", "Management", "Home", new { id = false }, null);
Thanks

MVC 4 Partial with separate Controller and View

I've developed ASP.NET Forms for some time and now am trying to learn MVC but it's not making total sense how to get it to do what I want. Perhaps I need to think about things differently. Here is what I'm trying to do with a made up example:
Goal - Use a partial file, which can be placed anywhere on the site which will accept a parameter. That parameter will be used to go to the database and pass back the resulting model to the view. The view will then display one or more of the models properties.
This isn't my code, but shows what I'm trying to do.
File: Controllers/UserController.cs
[ChildActionOnly]
public ActionResult DisplayUserName(string userId)
{
MyDataContext db = new MyDataContext()
var user = (from u in db.Users where u.UserId = userId select u).FirstOrDefault();
return PartialView(user);
}
File: Views/Shared/_DisplayUserName.cs
#model DataLibrary.Models.User
<h2>Your username is: #Model.UserName</h2>
File: Views/About/Index.cshtml
#{
ViewBag.Title = "About";
}
<h2>About</h2>
{Insert Statement Here}
I know at this point I need to render a partial called DisplayUserName, but how does it know which view to use and how do I pass my userId to the partial?
It's what I expect is a very basic question, but I'm yet to find a tutorial which covers this.
Thanks in advance for your help.
You should call Html.Action or Html.RenderAction like:
#Html.Action("DisplayUserName", "User", new {userId = "pass_user_id_from_somewhere"});
Your action should be like:
[ChildActionOnly]
public ActionResult DisplayUserName(string userId)
{
MyDataContext db = new MyDataContext()
var user = (from u in db.Users where u.UserId = userId select u).FirstOrDefault();
return PartialView("_DisplayUserName", user);
}
This should do the trick.
I always make sure to close the MyDataContext... Maybe enclose everything in a using statement... If you notice when VS does it for you they create the entity as a private variable in the Controller Class (outside of the controllers) and then close it with the dispose method... Either way I believe you need to make sure those resources are released to keep things running smooth. I know it's not in the question but I saw that it looked vulnerable.

TempData not working for second request in MVC4

I have never seen this before and am stumped. I have the following controller sequence:
/// <summary>
/// Helper method to store offerId to TempData
/// </summary>
/// <param name="offerId"></param>
private void StoreOfferInTempData(string offerId)
{
if (TempData.ContainsKey(SelectedOfferKey))
TempData.Remove(SelectedOfferKey);
TempData.Add(SelectedOfferKey, offerId);
}
[HttpPost]
[AllowAnonymous]
public virtual ActionResult Step1(MyViewModel model)
{
if (ModelState.IsValid)
{
StoreOfferInTempData(model.SelectedOfferId);
return RedirectToAction(MVC.Subscription.Register());
}
MySecondViewModel model2 = new MySecondViewModel { OfferId = model.SelectedOfferId };
return View(model2);
}
[HttpGet]
[AllowAnonymous]
public virtual ActionResult Register()
{
string offerId = TempData[SelectedOfferKey] as string; //we get a valid value here
... error handling content elided ...
RegisterViewModel model = new RegisterViewModel { OfferId = offerId };
return View(model);
}
[HttpPost]
[AllowAnonymous]
public virtual ActionResult Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
CreateCustomerResult result = CustomerService.CreateAccount(model.Email, model.NewPassword);
if (result.Success)
{
... content elided; storing customer to Session ...
MyMembershipProvider.PersistUserCookie(result.Principal, true);
//need to store our selected OfferId again for use by the next step
StoreOfferInTempData(model.OfferId);
return RedirectToAction(MVC.Subscription.Payment());
}
model.ErrorMessage = result.ErrorMessage;
}
return View(model);
}
[HttpGet]
public ActionResult Payment()
{
string offerId = TempData[SelectedOfferKey] as string; //this is null???
... content elided ...
return View(model);
}
The first round of storage to TempData behaves as expected. The value is present in the subsequent HttpGet method and is marked for deletion such that it is no longer there when I go to add it again. However, on the third HttpGet method, it returns null.
I have tried using different Keys for each round with no change. I can assure you that at no time other than those displayed am I checking TempData, so I see no way the value would be marked for deletion somehow. Also, it fails in the Payment method whether it has an [AllowAnonymous] attribute or not (so not due to any http to https switch or anything like that.
Seems like it must be something very simple, but my searches have turned up nothing. Any help greatly appreciated.
UPDATE: On further inspection, it seems my whole context is hosed on this step, for some reason. We're using IoC in the controllers, but none of the IoC-instantiated items are there. The mystery deepens.
The lifespan of TempData is only until it's read back out or the next request has processed (which ever comes first). You shouldn't be relying on TempData if you're going in to two (or three) requests. Instead, use the session or a database.
The purpose of TempData is to hand-off information between requests not to perpetuate until you clear it (that's what sessions are for).
Aha! Well, this is obscure enough that I hope it helps someone else. Turns out, I had forgotten to run my T4MVC.tt file after creating the Payment() actions, so the RedirectToAction taking an MVC.Subscription.Payment() action was not instantiating the controller properly. I'm not clear on all the underlying magic here, but if you run into this and are using T4MVC.tt, make sure you ran it!
Comments on why this would be are welcome.
Use TempData.Keep("key") to retain values between multiple post-backs

how to use built-in content type negotiation and just get access to the decision

I wanted to take advantage of built-in content negotiator and just get access to decision what formatter is going to be used. I don't want to use Request.Headers.Accept and check for whether it is json or xml content type because there lot of things are involved in that decision. Is there a way I can check at controller level or override any class that tells me what formatter going to be used OR what request content type is?
thanks in advance.
You can run conneg manually:
var conneg = Configuration.Services.GetContentNegotiator();
var connegResult = conneg.Negotiate(
typeof(YOUR_TYPE), Request, Configuration.Formatters
);
And use the output whichever way you want:
//the valid media type
var mediaType = connegResult.MediaType;
//do stuff
//the relevant formatter
var formatter = connegResult.Formatter;
//do stuff
If you want to see what is going on then install a TraceWriter and you will see what the conneg does.
A TraceWriter looks something like:
public class TraceWriter : ITraceWriter {
public bool IsEnabled(string category, TraceLevel level) {
return true;
}
public void Trace(HttpRequestMessage request, string category, TraceLevel level, Action<TraceRecord> traceAction) {
var rec = new TraceRecord(request, category, level);
traceAction(rec);
Log(rec);
}
private void Log(TraceRecord record) {
Console.WriteLine(record.Message);
}
}
and is installed like this,
config.Services.Replace(typeof(ITraceWriter), new TraceWriter());
If you want to manually invoke conneg then you can use,
config.Services.GetContentNegotiator().Negotiate(...)
Tugberk has a blog on this. Have a look.