Asp MVC 3: Modifiy Values Sent to View - asp.net-mvc-3

as far as I understand a ModelBinder can generate class instances out of routedata/formdata.
What I'm looking for is a way to manipulate the data handed over to the view before it is consumed by the view.
What are the possiblities? Do I miss something obvious?
Thanks in advance!
EDIT
I don't want to send clear IDs to the client but encrypt them (at least in edit cases). As it happens very often I want this step as much as possible automated.
I look for something like a ModelBinder or a Attribute to attach to a method/viewmodel/...
Example:
GET
public ActionResult Edit(int id)
{
var vm = new EditArticleViewModel();
ToViewModel(repository.Get<Article>(id), vm);
return View(vm); // id is something like 5 and should be encryped before being used by the view
}
View
#model EditArticleViewModel
<div>
#Html.HiddenFor(x => x.Id) <!-- x.Id should be encrypted, not just "5" -->
...
</div>
Lg
warappa

You could do something with an action filter:
public class EncryptIDAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var vm = filterContext.Controller.ViewData.Model as EditArticleViewModel;
if(vm != null)
{
vm.ID = SomeMethodToEncrypt(vm.ID);
}
}
}
and apply it to any relevent actions:
[EncryptID]
public ActionResult Edit(int id)
{
var vm = new EditArticleViewModel();
ToViewModel(repository.Get<Article>(id), vm);
return View(vm);
}
When the page is then posted you can use a model binder to decrypt the id.
If you then wanted to apply this across multiple view models you could look at creating a custom data annotation which flags a property to be encrypted. In your action filter you can then look for any properties with this data annotation and encrypt them accordingly.

You could write a custom HiddenFor helper method that will automatically encrypt the value:
public static class HiddenExtensions
{
public static MvcHtmlString HiddenForEncrypted<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> ex)
{
var metadata = ModelMetadata.FromLambdaExpression(ex, htmlHelper.ViewData);
var name = ExpressionHelper.GetExpressionText(ex);
var value = metadata.Model;
var encryptedValue = SomeFunctionToEncrypt(value);
return htmlHelper.Hidden(name, encryptedValue);
}
}
As an alternative you could use the Html.Serialize helper in the MVCFutures assembly that does this under the covers.
So basically you will write in your view:
#Html.Serialize("id", Model.Id, SerializationMode.Encrypted)
and in your controller:
public ActionResult Edit([Deserialize(SerializationMode.Encrypted)]int id)
{
...
}

Related

How to use different type of parameter without creating new view?

I would like to create a list with a string and an int value at the same time like follows:
#Html.ActionLink("Back to List", "IndexEvent", new { location = "location" })
and
#Html.ActionLink("Back to List", "IndexEvent", new { locationID = 1 })
It didn't work. I guess MVC controller didn't get the type difference of parameter. So, I had to make a new Action as "IndexEvenyByID" but it requires to have a new view. Since I wanted to keep it simple, is there any way to use same view with respect to different parameters?
Try adding two optional parameters to the IndexEvent action like this:
public ActionResult IndexEvent(string location = "", int? locationID = null)
This should not require a new view or view model. You should have two actions as you have described, but the code could be as follows:
Controller
public ActionResult GetEvents(string location){
var model = service.GetEventsByLocation(location);
return View("Events", model);
}
public ActionResult GetEventsById(int id){
var model = service.GetEventsById(id);
return View("Events", model);
}
Service
public MyViewModel GetEventsByLocation(string location){
//do stuff to populate a view model of type MyViewModel using a string
}
public MyViewModel GetEventsById(int id){
//do stuff to populate a view model of type MyViewModel using an id
}
Basically, if your View is going to use the same view model and the only thing that is changing is how you get that data, you can completely reuse the View.
If you really want to stick to a single action and multiple type, you could use a object parameter.
public ActionResult GetEvents(object location)
{
int locationID;
if(int.TryParse(location, out locationID))
var model = service.GetEventsByID(locationID);
else
var model = service.GetEventsByLocation(location as string);
return View("Events", model);
}
Something like that (Not completly right but it gives you an idea). This, however, wouldn't really be a "clean" way to do it IMO.
(Edit)
But the 2 actions method is still by far preferable (eg. What happens if we're able to parse a location name into a int?)

how to iterate ViewBag or how can I copy the values of viewBag from one Action to another Action

I have a base Controller like follow
public abstract class BaseController
{
protected ActionResult LogOn(LogOnViewModel viewModel)
{
SaveTestCookie();
var returnUrl = "";
if (HttpContext != null && HttpContext.Request != null && HttpContext.Request.UrlReferrer != null)
{
returnUrl = HttpContext.Request.UrlReferrer.LocalPath;
}
TempData["LogOnViewModel"] = viewModel;
return RedirectToAction("ProceedLogOn", new { returnUrl });
}
public ActionResult ProceedLogOn(string returnUrl)
{
if (CookiesEnabled() == false)
{
return RedirectToAction("logon", "Account", new { area = "", returnUrl, actionType, cookiesEnabled = false });
}
var viewModel = TempData["LogOnViewModel"] as LogOnViewModel;
if (viewModel == null)
{
throw new NullReferenceException("LogOnViewModel is not found in tempdata");
}
//Do something
//the problem is I missed the values which are set in the ViewBag
}
}
and another Controller
public class MyController : BaseController
{
[HttpPost]
public ActionResult LogOn(LogOnViewModel viewModel)
{
// base.LogOn is used in differnet controller so I saved some details in view bag
ViewBag.Action = "LogonFromToolbar";
ViewBag.ExtraData = "extra data related only for this action";
return base.LogOn(viewModel);
}
}
the problem is I missed the view bag values in ProceedLogOn action method.
I have the values in Logon method in BaseController.
How can I copy the values of ViewBag from one Action to another Action?
So I can not simply say this.ViewBag=ViewBag;
because ViewBag doesn't have setter. I was thinking of Iterating through viewbag.
I tried ViewBag.GetType().GetFields() and ViewBag.GetType().GetProperties() but they return nothing.
ViewData reflects ViewBag
You can iterate the values you've stored like this :
ViewBag.Message = "Welcome to ASP.NET MVC!";
ViewBag.Answer = 42;
foreach (KeyValuePair<string, object> item in ViewData)
{
// if (item.Key = "Answer") ...
}
This link should also be useful
I'm afraid I don't have the answer how to copy ViewBag.
However, I would never use ViewBag that way.
ViewBag is some data the Controller gives to the View to render output if someone does not like to use ViewModel for some reasons. The View should never know anything about the Controller but your ViewBag is holding a ActionName ;).
Anyway, the ProceedLogOn action method has pretty much parameters which is ... not a nice code actually so why hesitate to add more parameters which are currently being hold in MyController.Logon ViewBag? Then inside method ProceedLogOn you have what you want.
;)

ASP.NET MVC 3 Viewmodel Pattern

I am trying to work out the best way of using a viewmodel in the case of creating a new object.
I have a very simple view model that contains a contact object and a select list of companies.
private ICompanyService _Service;
public SelectList ContactCompanyList { get; private set; }
public Contact contact { get; private set; }
public ContactCompanyViewModel(Contact _Contact)
{
_Service = new CompanyService();
contact = _Contact;
ContactCompanyList = GetCompanyList();
}
private SelectList GetCompanyList()
{
IEnumerable<Company> _CompanyList = _Service.GetAll();
return new SelectList(_CompanyList, "id", "name");
}
I then have contact controller that uses this viewmodel and enable me to select a related company for my contact.
[Authorize]
public ActionResult Create()
{
return View(new ContactCompanyViewModel(new Contact()));
}
My issue is with the create method on the controller.
[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Contact _Contact)
{
try
{
_Service.Save(_Contact);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
The problem is that the view returns an empty contact object, but! the company id is populated, this is because the dropdown list explicitly declares its field name.
#Html.DropDownList("parent_company_id",Model.ContactCompanyList)
The standard html form fields pass the objects values back in the format of contact.forename when using the HTML.EditorFor helper...
#Html.EditorFor(model => model.contact.forename)
I can access them if I use a FormCollection as my create action method paremeter and then explicitly search for contact.value but I cannot use a Contact object as a parameter to keep my code nice and clean and not have to build a new contact object each time.
I tried passing the actual view model object back as a parameter but that simply blows up with a constructor error (Which is confusing seeing as the view is bound to the view model not the contact object).
Is there a way that I can define the name of the Html.EditFor field so that the value maps correctly back to the contact object when passed back to the create action method on my controller? Or Have I made some FUBAR mistake somewhere (that is the most likely explanation seeing as this is a learning exercise!).
Your view model seems wrong. View models should not reference any services. View models should not reference any domain models. View models should have parameterless constructors so that they could be used as POST action parameters.
So here's a more realistic view model for your scenario:
public class ContactCompanyViewModel
{
public string SelectedCompanyId { get; set; }
public IEnumerable<SelectListItem> CompanyList { get; set; }
... other properties that the view requires
}
and then you could have a GET action that will prepare and populate this view model:
public ActionResult Create()
{
var model = new ContactCompanyViewModel();
model.CompanyList = _Service.GetAll().ToList().Select(x => new SelectListItem
{
Value = x.id.ToString(),
Text = x.name
});
return View(model);
}
and a POST action:
[HttpPost]
public ActionResult Create(ContactCompanyViewModel model)
{
try
{
// TODO: to avoid this manual mapping you could use a mapper tool
// such as AutoMapper
var contact = new Contact
{
... map the contact domain model properties from the view model
};
_Service.Save(contact);
return RedirectToAction("Index");
}
catch
{
model.CompanyList = _Service.GetAll().ToList().Select(x => new SelectListItem
{
Value = x.id.ToString(),
Text = x.name
});
return View(model);
}
}
and now in your view you work with your view model:
#model ContactCompanyViewModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.SelectedCompanyId, Model.CompanyList)
... other input fields for other properties
<button type="submit">Create</button>
}

How to add a bind list to the TryUpdateModel in asp.net mvc3

I have the following action method:-
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
Assessment a = elearningrepository.GetAssessment(id);
try
{
if (TryUpdateModel(a))
{
elearningrepository.Save();
return RedirectToAction("Details", new { id = a.AssessmentID });
}
}
//code does here
but I can not write something like if (TryUpdateModel(a, "Assessment", new string { "Date"})) to specify that I only allow the Date property to be updated.
So how I can add a bind list to the above if (TryUpdateModel(a))?
BR
but I can not write something like
if (TryUpdateModel(a, "Assessment", new string { "Date"}))
That's because you should write it like this, since the allowed properties argument represents a string array:
if (TryUpdateModel(a, "Assessment", new[] { "Date" }))
{
}
I would suggest that you stay away from using TryUpdateModel in general.
The repository usually has an update method that sets the entityState to modified before Save() is called, i cannot see that in the code above.
If your goal is to display a record and only allow date to be saved, then create a view for that model, and render fields with:
This sets the model for the view:
#model YourNamespace.Models.Assessment
#Html.DisplayFor(model=>model.propertyToDisplay)
on the items you only want to display, and a
#Html.EditorFor(model=>model.Date)
In your action controller you take the properties you want to bind to as input parameters:
Edited
class Assessment
{
public int Id { get; set; }
public DateTime Date { get; set; }
//Other properties
}
public ActionResult Edit(int Id, DateTime Date)
{
var assessment = elearningrepository.GetAssessment(id);
assessment.Date = Date;
elearningrepository.UpdateAssessment(assessment);
elearningrepository.Save();
//Redirect to action Detail
}
In this case the model binder should just bind to Id, and Date, so even if someone tries to post other values (editing the html form is easy), parameters in ActionResult should be named exactly as in the Model and use that to fetch and update the entity.
You should validate that the user actually can access and edit that id, or as an alternative use MVC Security Codeplex to check that the Id parameter has not been tampered with. it is really easy and convenient to use, but that is another discussion.
As an alternative you can use an attribute like this, described in this blog, but I don't use that myself:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create( [Bind(Include="Id,Date")] Assessment assessment)
i tried this an it works fine
string[] allowedProperties = new[] { "Date" };
try
{
if (TryUpdateModel(a, allowedProperties))
{

Can I reuse a remote validation action in MVC3

I am using a Remote validation attribute on my view model to validate a Bank Account that is specified for my Company:
ViewModel:
[Remote("CheckDefaultBank", "Company")]
public string DefaultBank
{
This in the controller I have:
[HttpGet]
public JsonResult CheckDefaultBank(string defaultBank)
{
bool result = BankExists(defaultBank);
return Json(result, JsonRequestBehavior.AllowGet);
}
That all works well. But, I have two other banks related to my company as well. However, when the remote validation js calls the action it uses a parameter mactching the field name of "DefaultBank"... so I use that as a parameter in my action.
Is there some attribute I can add in the view so that it will use a parameter of say "bankId" on the ajax get so I don't need an action for each field which are basically exactly the same?
The goal here is to eliminate now having to have this in my controller:
[HttpGet]
public JsonResult CheckRefundBank(string refundBank)
{
bool result = BankExists(defaultBank);
return Json(result, JsonRequestBehavior.AllowGet);
}
[HttpGet]
public JsonResult CheckPayrollBank(string payrollBank)
{
bool result = BankExists(defaultBank);
return Json(result, JsonRequestBehavior.AllowGet);
}
I was hoping I could do something like this in the view:
#Html.EditorFor(model => model.DefaultBank, new { data-validate-parameter: bankId })
This way I could just use the same action for all of the Bank entries like:
[HttpGet]
public JsonResult CheckValidBank(string bankId)
{
bool result = BankExists(bankId);
return Json(result, JsonRequestBehavior.AllowGet);
}
Possible?
For just such a situation, I wrote a RemoteReusableAttribute, which may be helpful to you. Here is a link to it: Custom remote Validation in MVC 3
Since MVC uses the default model binder for this, just like a normal action method. You could take a FormsCollection as your parameter and lookup the value. However, I personally would find it much easier to just use several parameters to the function, unless you start having dozens of different parameters.
You could also write a custom model binder, that would translate the passed parameter to a generic one.
Consider encapsulating the logic, "BankExists" in this case into a ValidationAttribute (Data Annotations Validator). This allows other scenarios as well.
Then use a wrapper ActionResult like the one below, which lets you pass in any validator.
[HttpGet]
public ActionResult CheckRefundBank(string refundBank)
{
var validation = BankExistsAttribute();
return new RemoteValidationResult(validation, defaultBank);
}
Here is the code for the ActionResult that works generically with Validators.
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
public class RemoteValidationResult : ActionResult
{
public RemoteValidationResult(ValidationAttribute validation, object value)
{
this.Validation = validation;
this.Value = value;
}
public ValidationAttribute Validation { get; set; }
public object Value { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var json = new JsonResult();
json.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
if (Validation.IsValid(Value))
{
json.Data = true;
}
else
{
json.Data = Validation.FormatErrorMessage(Value.ToString());
}
json.ExecuteResult(context);
}
}
As an extra enhancement consider creating a Controller Extension method to dry up your return call even more.

Resources