I have a web-API project and a simple class with a few properties, some are marked <JsonIgnore>.
In my MVC-controller I put Return Json(instanceOfMyClass, JsonRequestBehavior.AllowGet). All members are serialized.
I put Return Json(Of MyClass)(instanceOfMyClass) in my WEBAPI-controller. Only the members I intend to serialize are present.
How can I ignore these properties independent of the controller that's going to serialize.
The JsonResult in MVC does not actually use JSON.NET which is why [JsonIgnore] is not working. Instead it uses the JavaScriptSerializer class.
To make the JavaScriptSerializer skip a property, you can you the [ScriptIgnore] attribute on your model property.
An alternative would be to make a custom ActionResult that uses JSON.NET's JsonConvert to serialize the object which would then honor the [JsonIgnore] attribute.
In case it helps anyone, it didn't seem possible or straightforward to use [ScriptIgnore] in my .net Core app, so I did this:
public IActionResult Index()
{
Response.ContentType = "text/json";
return Content(JsonConvert.SerializeObject(instanceOfMyClass));
}
Related
public ActionResult Demo()
{
return View();
}
I really can't understand use of ActionResult in this context.
ActionResult is an abstract class, we use diff methods of ActionResult without creating it's object.
Logically how does it work internally ?
View() is a regular function that returns some concrete subclass of ActionResult (in this case, ViewResult).
By default the framework allows us to return various types of responses to requests. ActionResult is the abstract class that is inherited by various other classes that can be returned to the client. So here we return the superclass type to provide flexibility to the developer to decide which type of result to provide.
This link describes it all:
https://msdn.microsoft.com/en-us/library/system.web.mvc.actionresult(v=vs.118).aspx
I have a custom viewmodel which serialized using a JsonResult. The ViewModel has some properties which have to be public, but at the same time these properties should not be visible in the resulting Json output.
I've already tried using the [NonSerialized] attribute, but that did not seem to have any effect.
Is there any simple way to do this? Or would I have to code my own result type (in which case I probably won't bother)?
You can put a [ScriptIgnore] attribute on the members that shouldn't be serialized. See ScriptIgnoreAttribute Class in MSDN for an example.
Just create an interface to return instead of a class.
public interface IMyViewModel {
string MyPublicProperty { get; set; }
}
Then create a class that inherits the interface
public class MyViewModel : IMyViewModel {
public string MyPublicProperty { get; set; }
public string MyNotSoPublicProperty { get; set; }
}
And return the interface, not the class, in the Controller Action
public JsonResult MyJson(){
IMyViewModel model = new MyViewModel();
return Json(model);
}
And the resulting JSON will be
{
'MyPublicProperty': ''
}
One of the challenges in client-side scripting is, that if you're changing your classes, you have no idea whether you're destroying the client-side implementation or not. If you use interface types in your JSON, you understand that if you change the interface, you're doing something that potentially may be killing the client side implementation. And it also saves you from double-checking the client side in vain if you're changing something that is NOT in the inteface (thus not being serialized).
Also, many times, your ViewModels might have large collections or complex types in them that you don't necessarily want to output to the client. These might take a long time to serialize or expose information that simply does not belong into the client code. Using interfaces will make it more transparent to know what is being in the output.
Also, using attributes such as [ScriptIgnore] on a property only applies to a specific scenario (JavaScript Serialization) forcing you to face the exact same problem if you're later serializing to XML for example. This would unnecessarily litter your viewmodels with tons of attributes. How many of them you really want in there? Using intefaces applies anywhere and no viewmodel needs to be littered with extra attributes.
Have a look at JSON.NET from James Newton-King. It'll do what you're looking for.
Extend the JavaScriptConverter class to not include properties with the NonSerializedAttribute. Then you can create a custom ActionResult that uses your JavaScriptConverter to serialize the object.
This creates a solid and testable class without having to (re)generate wrapper classes or using anonymous objects.
You can create a wrapper class that exposes only those properties that you want in the JsonResult. In the example below, Cow has 2 properties - "Leg" and "Moo". Suppose you want to only expose "Leg" as a property. Then
Dim cw as CowWrapper = New CowWrapper(c)
will return a wrapper class that only exposes "Leg". This is also useful for things like DataGridView if you only want to display some subset of the properties.
Public Class Cow
Public ReadOnly Property Leg() as String
get
return "leg"
end get
end Property
Public ReadOnly Property Moo() as String
get
return "moo"
end get
end Property
end class
Public Class CowWrapper
Private m_cow as Cow = Nothing
Public Sub New(ByVal cow as Cow)
m_cow = cow
end Sub
m_cow = cow
Public ReadOnly Property Leg() as String
get
return m_cow.Leg()
end get
end Property
end Class
Not exactly the answer you're looking for, but you can cheat Json() using the following code and anonymous classes:
MyModel model = ...;
return Json(new MyModel {model.Prop1, model.Prop2});
I needed the answer to this for ASP.NET Core 6.x and couldn't find it.
I finally found the answer and it is :
[System.Text.Json.Serialization.JsonIgnore]
Here's an example in a class
class Sample{
// Item will not be serialized
[System.Text.Json.Serialization.JsonIgnore]
String Item{get;set;}
// Count will be serialized
int Count{get;set;}
}
Suppose I want to allow to select our entity (from a dropdown, etc) on a page, let's say Product. As a result I may receive this:
public ActionResult SelectedAction(Guid productId)
{
}
But, I want to use model binders power, so instead I write model binder to get my product from repository and instead use
public ActionResult SelectedAction(Product product)
{
if (ModelState.IsValid) {} else {}
}
My model binder will set model state to false if product is invalid.
Now, there're problems with this approach:
It's not always easy to use strongly-typed methods like Html.ActionLink(c => c.SelectedAction(id)) since we need to pass Product, not id.
It's not good to use entities as controller parameters, anyway.
If model state is invalid, and I want to redirect back and show error, I can't preserve selected product! Because bound product is not set and my id is not there. I'd like to do RedirectToAction(c => c.Redisplay(product)) but of course this is not possible.
Now, seems like I'm back to use "Guid productId" as parameter... However, there's one solution that I'd like to present and discuss.
public class EntityViewModel<T> where T : BaseEntity
{
public EntityViewModel(Guid id)
{
this.Id = id;
}
public static implicit operator EntityViewModel<T>(T entity)
{
return new EntityViewModel<T>(entity.Id);
}
public override string ToString()
{
return Id.ToString();
}
public Guid Id { get; set; }
public T Instance { get; set; }
}
Now, if I use
public ActionResult SelectedAction(EntityViewModel<Product> product)
{
if (ModelState.IsValid) {} else {}
}
all the problems are solved:
I can pass EntityViewModel with only Id set if I have only Id.
I don't use entity as parameter. Moreover, I
can use EntityViewModel as property inside another ViewModel.
I can pass EntityViewModel back to RedirectToController and it will keep its Id value, which will be
redisplayed to user along with the validation messages (thanks to MVCContrib and ModelStateToTempData / PassParametersDuringRedirect).
The model binder will get Instance from the repository and will set model state errors like "Not found in database" and so on. And I can use things like ActionLink(c => c.Action(Model.MyProductViewModelProperty)).
The question is, are there any drawbacks here? I can't see anything bad but I'm still new to MVC and may miss some important things. Maybe there're better and approved ways? Maybe this is why everybody uses entity IDs as input parameters and properties?
Overall that looks like a good appoach to me...
As an alternative, you could use POCO for your viewmodel then I think all 3 problems would be solved automatically. Have you seen the Automapper project that allows an Entity to DTO approach? This would give you more flexibility by separating you ViewModel from your EntityModel, but really depends on the complexity of you application you are building.
MVC's ViewDataExtensions might also be useful instead of creating custom containers to hold various viewmodel objects as you mention in number 2.
MVCContrib's ModelStateToTempData should work for any serializable object (must be serializable for any out of process sessionstate providers eg. SQL, Velocity etc.), so you could use that even without wrapping your entity classes couldn't you?
Let's say I have an interface like:
interface IThing {
int Id { get; set; }
string Title { get; set; }
}
And in ASP.NET MVC I have a form that posts to a controller action like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult NewThing([Bind(Exclude = "Id")] SimpleThing thing) {
// code to validate and persist the thing can go here
}
Where SimpleThing is a concrete class that just barely implements IThing.
However, I would like all my methods to deal with the interface. I have a data assembly that uses NHiberate and its own IThing implementation (let's call it RealThing). I can't pass the SimpleThing to it because it will complain about an "unknown entity".
Does anyone have any ideas about a cleaner way to do this? I was thinking about something along the lines of using a factory class. But how would I get the MVC form binder to use it?
Thanks!
You can use custom model binders. However article on msdn is totally useless. So better to employ search and find something better. There are planaty of articles available.
I came up with two approaches to this.
The first was to add code to my NHibernate Repository class to translate the simple POCO type used by the MVC controller (SimpleThing) to the type of entity that NHibernate wanted (RealThing):
/// <summary>
/// A NHibernate generic repository. Provides base of common
/// methods to retrieve and update data.
/// </summary>
/// <typeparam name="T">The base type to expose
/// repository methods for.</typeparam>
/// <typeparam name="K">The concrete type used by NHibernate</typeparam>
public class NHRepositoryBase<T, K>
: IRepository<T>
where T : class
where K : T, new()
{
// repository methods ...
/// <summary>
/// Return T item as a type of K, converting it if necessary
/// </summary>
protected static K GetKnownEntity(T item) {
if (typeof(T) != typeof(K)) {
K knownEntity = new K();
foreach (var prop in typeof(T).GetProperties()) {
object value = prop.GetValue(item, null);
prop.SetValue(knownEntity, value, null);
}
return knownEntity;
} else {
return (K)item;
}
}
So, any method in the repository can call GetKnownEntity(T item) and it will copy the properties of the item you pass in to the type that NHibernate wants. Obviously this felt a bit clunky, so I looked in to custom model binders.
In the second approach, I created a custom model binder like this:
public class FactoryModelBinder<T>
: DefaultModelBinder
where T : new()
{
protected override object CreateModel(ControllerContext controllerContext,
ModelBindingContext bindingContext,
Type modelType) {
return new T();
}
}
Then I registered that in Global.asax.cs with:
ModelBinders.Binders.Add(typeof(IThing),
new FactoryModelBinder<RealThing>());
And it works fine with a Controller Action that looks like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult NewThing([Bind(Exclude = "Id")] IThing thing) {
// code to process the thing goes here
}
I like the second approach, but most of my dependency injection stuff is in the Controller class. I don't like to have to add all these ModelBinder mappings in Global.asax.cs.
There were some good suggestions here and I actually came up with a solution that works. However, I ended up with something altogether. I just created models specific for the form data I was posting and used the default model binder.
Much simpler and it allows me to capture data that isn't part of my domain model (ie. like a "comments" field).
This is not dirrect unswer to your question.
We use slightly different approach to deal with same problem you have. Our Controllers accepts DTOs that match persistent entity field by field. Then we user AutoMapper to create persisten entities that will go to the database. This eliminates unnessesery interfaces and locks down public facing API (means that renaming persistance object's field will not break our client code).
I want to configure my model binders with Nhibernate:
So I have:
<object id="GigModelBinder" type="App.ModelBinders.GigModelBinder, App.Web" singleton="false" >
<property name="VenueManager" ref="VenueManager"/>
<property name="ArtistManager" ref="ArtistManager"/>
I have an attribute which marks controller actions so that they use the correct model binder i.e.
[AcceptVerbs("POST")]
public ActionResult Create([GigBinderAttribute]Gig gig)
{
GigManager.Save(gig);
return View();
}
This works fine and my GigModelBinder has the correct VenueManger and ArtistManager injected
However if in application Start I add:
System.Web.Mvc.ModelBinders.Binders.Add(typeof(App.Shared.DO.Gig), new GigModelBinder());
and in a controller action use :
UpdateModel<Gig>(gig);
for example:
[AcceptVerbs("POST")]
public ActionResult Update(Guid id, FormCollection formCollection)
{
Gig gig = GigManager.GetByID(id);
UpdateModel<Gig>(gig);
GigManager.Save(gig);
return View();
}
The VenueManger and ArtistManager has NOT been injected into the GigModelBinder.
Any ideas what I'm doing wrong?
In the first example you go via Spring.NET to retrieve your object. That means it'll look for all the dependencies and stick them into your object and all works well.
In the second example you forget about Spring.NET all along and just create an ordinary instance of a class.
The line where you register your binder should look like this:
System.Web.Mvc.ModelBinders.Binders[typeof(App.Shared.DO.Gig)] = context.GetObject("GigModelBinder");
where context is either an IApplicationContext or a IObjectFactory instance from Spring.NET package.
Best regards,
Matthias.