Adding properties based on lambda expressions - c#

I am using C# to create a view model that I later serialize into Json for use with KnockoutJs.
Now I need to add information on a property level if a certain user has access to view and/or edit the property.
Since Javascript has little to no reflection possibilities I want to do this with C# before serializing to Json.
My view model could look like this:
class ViewModel {
public string Name { get; set; }
}
I want to use a User Access Service that would take the user, which has some Roles, and the view model and go through the Roles and apply the access rights. Something like this:
class AccessService {
public void Apply(IUser user, ViewModel viewModel) {
if(user.Roles.Contains(Roles.Admin)) {
viewModel.AllowRead(vm => vm.Name);
}
}
}
The viewModel.AllowRead(..) method would be an extension method that would take an object (or maybe an interface or type if necessary) and this is where the magic would happen.
I would like the result of this operation to be that the viewModel would get a new property with the name CanRead which in turn would have a boolean property with the name Name.
The resulting viewModel would look like this:
class ViewModel {
public string Name { get; set; }
public object CanRead { // Could non anonymous as well.
return new {
Name = true;
};
}
}
Can this be done with dynamics or do I have to use Reflection.Emit? I'm not asking for "show me the codez". I just want to know if my idea is mental or if it's possible.
Any ideas?

I think it would be possible and you could use the Lambdanator to help achieve this.
Example usage:
Lambda.Reflect<SomeClass>(x => x.AMethod());
Lambda.Reflect<SomeClass>(x => x.AProperty);
Lambda.Reflect(() => local);

Related

Razor Tag Helper - Binding to a Collection

Using ASP.NET Core 2.2 I am trying to build a Tag Helper that will bind to a ModelExpression representing a collection of SelectListItems. I then want to be able to access and iterate over the SelectListItems contained in the list.
In code terms, it looks something like this...
[HtmlTargetElement("check-box-list", Attributes = "asp-for)]
public class CheckboxListTagHelper : TagHelper
{
// Where the model is an IEnumerable<SelectItemList>
[HtmlAttributeName("asp-for")]
public ModelExpression AspFor { get; set; }
public override async Task ProcessAsync(
TagHelperContext context, TagHelperOutput output)
{
// I want to be able to reference the IEnumerable<SelectListItem>
// represented by the model
var modelItems = AspFor.Model as IEnumerable<SelectListItem>; // WRONG!
...
}
}
I assume I need to use the ModelExpression.MetaData or the ModelExpression.ModelExpolorer properties, but I can't find any information that points me in the right direction.
An inelegant fix has been to supply the same model as an additional property:
[HtmlAttributeName("select-item-list")]
public IEnumerable<SelectListItem> SelectItemList { get; set; }
But this looks messy in the view:
<check-box-list
asp-for="SelectedAuthorityLevels"
checkbox-list="Model.SelectedAuthorityLevels" />
as you are binding to the same model property twice.
Any guidance would be much appreciated.
The answer was, in the end, very easy - since I know the expression type, I can simply cast it as follows:
var selectList = AspFor.Model as IEnumerable<SelectListItem>;
if (selectList == null) {
var msg = "CheckboxList tag helper attribute 'asp-for' must of type " +
"IEnumerable<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>!";
throw new Exception(msg);
}

Ninject bind property on view models to filter

I'm fairly new to ninject so you'll have to forgive the potentially stupid question. I have been able to successfully bind my own custom filter to controller actions, however my question is can I do the same to a property on a ViewModel? My scenario is thus:
I have a view model with properties that look like this
public class CreateViewModel
{
...
[PopulateWith(typeof(Country))]
public IEnumerable<SelectListItem> Countries { get; set; }
...
}
the attribute is is a simple class
public class PopulateWithAttribute : Attribute
{
public Type Type { get; }
public PopulateWithAttribute(Type t)
{
Type = t;
}
}
and all I want to be able to do is have a block of code that will run when a new instance of CreateViewModel is created, that will take the database context to hydrate the enumeration. I'm not sure if even an action filter is the correct route to go down like how you make custom authorization / logging functionality. It doesn't seem that the BindFilter<> has anything that points to being able to bind on properties...
A point in any direction or to any resources would be great.

How can I display the client ID of a model property into a view?

In my view I want to be able to render out the ID of a model property independent of an associated control markup.
So for example, if my model was:
public class Author {
public string Title { get; set; }
public Address Address { get; set; }
}
In my view I would preferably like to be able to use an extension of HtmlHelper to render out the Title property's ID using a lambda. Something like:
#Html.GetClientIdFor(x => x.Title)
And this would output "Title".
In the case of nested objects, I would like:
#Html.GetClientIdFor(x => x.Address.PostCode)
to output "Address_PostCode".
I'm sure I can access the data from the ViewData.TemplateInfo object but from there I'm a little stuck.
Any ideas? I am using MVC4 but I would like a solution that worked for MVC3 too.
Unless I'm misunderstanding your question, there is an HtmlHelper that does exactly what you want: IdFor
#Html.IdFor(x => x.Address.PostCode) // Will return Address_PostCode
Although, that's for MVC4 and up. For MVC3, you will need to roll your own. See this SO.

Passing and returing data to a simple grid form using ASP.NET MVC

I have page with a simple table and advanced search form. I pass List<Customers> to the model:
View(List<Customers>);
So what is best way to pass and return data to the search form? I want to use validation or something but I think passing data through ViewData is not good idea. Any suggestions?
You should wrap all your data that is required by you view in a model specific to that view. The advantage to this is you could also include your search criteria in the model which would be empty at first but when your search posted, the model would automatically contain your search criteria so you could reload it when passing back the results. This will help maintain your state between post's as well.
This also allows all your view's data to be type safe where ViewData would not be.
Eg:
public class CustomerSearchViewModel
{
public List<Customer> Customers { get; set; }
// your search criteria if you want to include it
public string SearchFirstName { get; set; }
public string SearchLastName { get; set; }
public int SearchCustomerID { get; set; }
// etc...
}
When you return back the List<Customer> the search criteria would already be filled in your model from the post so your view can default the search criteria back to the corresponding controls (assuming your search results and search inputs controls are on the same view).
For example, in your post you would accept a CustomerSearchViewModel. Then all you need to do is get your list of customers and add it back to the model and return the same model.
// assuming you have accepted a CustomerSearchViewModel named model
model.Customers = GetCustomersForSearchCriteria(model.SearchFirstName,
model.SearchLastName, model.SearchCustomerID);
return View(model);
You could also add the validation attributes to your model properties to leverage the built in validation in MVC. This would not be possible if you were using ViewData to pass this data around.
You have to also consider the 'next guy'. It's cleaner when all the data that the view requires is located in a single class. This way they don't have to hunt through the code to discover if ViewData is being used and what data is actually being passed around in it.
ViewData is still an option for passing data but I try to minimize the use of it if at all possible.
Rather than passing just a list of items to your View, create a class which contains your list of items and any other data you might need, i.e. a ViewModel.
public class CustomerSearchViewModel {
public IEnumerable<Customer> Customers { get; set; }
public string SearchTerm { get; set; }
}
.....
var viewModel = new CustomerSearchViewModel {
Customers = customerList,
SearchTerm = searchTerm
};
return View(viewModel);

Limiting the data returned by a controller

I need advice on how to return a limited set of data from an MVC controller.
Lets say I have a class that is constructed like so:
public interface ICustomerExpose
{
string Name {get; set;}
string State {get; set;}
}
public interface ICustomer: ICustomerExpose
{
int Id {get; set;}
string SSN {get; set;}
}
public class Customer: ICustomer
{
...
}
In my MVC project I have a controller action that returns customer data. The project is actually more like a web service as there is no View associated with the data... we use the XmlResult (provided by the MVCContrib project). The controller action looks like this:
// GET: /Customer/Show/5
public ActionResult Show(int id)
{
Customer customer = Customer.Load(id);
... // some validation work
return new XmlResult((ICustomerExpose)customer);
}
The above controller code does not work like I want it to. What I want to happen is that only the Name and State properties are serialized and returned in the XmlResult. In practice the whole customer object is serialized including the data I definitely don't want exposed.
I know the reason this doesn't work: you can't serialize an interface.
One idea floated around the office was to simply mark the properties Name and State as [XmlIgnore]. However, this doesn't seem like a good solution to me. There might be other instances where I want to serialize those properties and marking the properties on the class this way prohibits me.
What is the best way to achieve my goal of only serializing the properties in the ICustomerExpose interface?
Addendum:
For those interested in what XmlResult does here are the relevant parts of it:
public class XmlResult : ActionResult
{
private object _objectToSerialize;
public XmlResult(object objectToSerialize)
{
_objectToSerialize = objectToSerialize;
}
/// <summary>
/// Serialises the object that was passed into the constructor
/// to XML and writes the corresponding XML to the result stream.
/// </summary>
public override void ExecuteResult(ControllerContext context)
{
if (_objectToSerialize != null)
{
var xs = new XmlSerializer(_objectToSerialize.GetType());
context.HttpContext.Response.ContentType = "text/xml";
xs.Serialize(context.HttpContext.Response.Output, _objectToSerialize);
}
}
}
You can try this, however I am not sure if it works with xml serializers:
return new XmlResult(new { customer.Name, customer.State });
See this related question which recommends using an anonymous type.
// GET: /Customer/Show/5
public ActionResult Show(int id)
{
Customer customer = Customer.Load(id);
... // some validation work
var result = from c in cusomter
select new
{
Name = c.Name,
State = c.State,
};
// or just
var result = new
{
Name = customer.Name,
State = customer.State,
};
return new XmlResult(result);
}
Consider using, just for this one problem, XML literals in VB9 rather than serialization. Seriously. Just give it 20 minutes of your time. There's many options.
http://www.hanselman.com/blog/TheWeeklySourceCode30VBNETWithXMLLiteralsAsAViewEngineForASPNETMVC.aspx
http://www.hanselman.com/blog/XLINQToXMLSupportInVB9.aspx
http://blogs.msdn.com/dmitryr/archive/2008/12/29/asp-net-mvc-view-engine-using-vb-net-xml-literals.aspx
http://haacked.com/archive/2008/12/29/interesting-use-of-xml-literals-as-a-view-engine.aspx
http://www.infoq.com/news/2009/02/MVC-VB
For what you're doing, returning XML as a poor-man's Web Service, this is tailor-made.
I ended up just doing the XmlIgnore as co-workers suggested, even though this left me with some undesirable (or so I thought) behaviors.
To get around the fact that XmlIgnore would continue hiding properties that I might want serialized later I asked another question trying to find a way to around that issue. Cheeso came up with a great answer making the XmlIgnore the best route (in my opinion) to take.

Categories