2

Base class: AbstractBuildBlock, Derived: TextBlock, ImageBlock, EmptyBlock, ... .

Blocks here: Site -> Pages[someIndex] -> Rows[someIndex] -> BuildBlocks

Fields BuildBlocks is of type AbstractBuildBlock, so when I'm saving Site to DB in BuildBlocks each record has descriminator AbstractBuildBlock. I try to do the next in BuildBlockRepository:

    switch(obj.ContentType)
            {
                case "text":
                    obj = obj as TextBlock;
                    context.BuildBlocks.Add(obj);
                    break;
            }

After obj = obj as TextBlock obj is null. The reason is that obj is of type AbstractBuildBlock. I found at msdn that this code should work:

BaseClass a = new DerivedClass()
DerivedClass b = a as DerivedClass

So I need reproduce this code at model binding. This is ajax request:

$('.saveSite').click(function () {
    $.ajax({
        url: '/Site/Update',
        data: { site: getSite() },
        method: 'POST',
        success: function (data) {
            console.log('Success save');
        },
        error: function (data) {
            debugBox(data);
        }
    });
});

and mvc action for this request

    public string Update(Site site)
    {
        siteRepository.Add(site);
        return "Success";
    }

So I send Site in json form, BuildBlocks that are in this site in form of json too, but of course their(blocks) type is not AbstractBuildBlock, they are all of TextBlock, ImageBlock, etc. and have fields with values.

The problem: Site has field BuildBlocks which type is AbstractBuildBlock and model binder do something like this:

buildBlock = new AbstractBuildBlock(); //loose derived classes fields and posibility to convert it in DerivedClass
buildBlocks.push(buildBlock)

but I need someting like this

switch(buildBlock.contenType) {
    case "text" : buildBlock = new TextBlock();buidlBlocks.push(buildBlock);
}
Nikita
  • 869
  • 1
  • 12
  • 30
  • 1
    Take a look at: http://stackoverflow.com/questions/7222533/polymorphic-model-binding, which I think may be helpful. BTW, it's clear that you've worked really hard on writing this question--many people asking on SO don't put effort into their questions, but you have. However, I think in this case, you maybe can simplify your question a bit, by focusing on `obj = obj as TextBlock;` and why it is isn't working. Gives us the full code for that, but focus less on the other stuff (keep just enough so that we have an idea of what you are doing). – DWright Aug 13 '16 at 17:29
  • 1
    You could use Json.NET for your model binder, as shown in [How to use Json.NET for JSON modelbinding in an MVC5 project?](https://stackoverflow.com/questions/23995210), them use a `JsonConverter` to deserialize to a concrete type based on the properties present in the JSON, as shown in [Deserializing polymorphic json classes without type information using json.net](https://stackoverflow.com/questions/19307752). – dbc Aug 13 '16 at 17:38

1 Answers1

0

JSON NET Custom deserializer not work at all

ASP MVC 5 How to read object from request with the help of JSON NET

look at answers in two links above, the correct ajax call is described

And the server code below

mvc action

public string Update(Site site)
    {
        TextBlock block = site.Pages[0].Rows[0].BuildBlocks[0] as TextBlock;
        siteRepository.Add(site);
        return "Success";
    }

AbstractJsonCreationConverter I've created it in Infrastructure folder

public abstract class AbstractJsonCreationConverter<T> : JsonConverter
{
    protected abstract T Create(Type objectType, JObject jsonObject);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType,
      object existingValue, JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var target = Create(objectType, jsonObject);
        serializer.Populate(jsonObject.CreateReader(), target);
        return target;
    }

    public override void WriteJson(JsonWriter writer, object value,
   JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

And in the same folder concrete class

public class JsonBuildBlockConverter : AbstractJsonCreationConverter<AbstractBuildBlock>
{
    protected override AbstractBuildBlock Create(Type objectType, JObject jsonObject)
    {
        var type = jsonObject["contentType"].ToString();
        switch(type)
        {
            case "text":
                return new TextBlock();
            default:
                return null;
        }
    }
}

and one more class in Infrastructure

internal class SiteModelBinder : System.Web.Mvc.IModelBinder
{
    public object BindModel(ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext)
    {
        // use Json.NET to deserialize the incoming Position
        controllerContext.HttpContext.Request.InputStream.Position = 0; // see: https://stackoverflow.com/a/3468653/331281
        Stream stream = controllerContext.RequestContext.HttpContext.Request.InputStream;
        var readStream = new StreamReader(stream, Encoding.UTF8);
        string json = readStream.ReadToEnd();
        return JsonConvert.DeserializeObject<Site>(json, new JsonBuildBlockConverter());
    }
}

The last class is ModelBinder that will be called to parse variables of type Site, to make it work you need to register it in Global.asax.cs in ApplicationStart()

 protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        ModelBinders.Binders.Add(typeof(Site), new SiteModelBinder()); //RegisterModelBinder for Site
    }

This is all.

Community
  • 1
  • 1
Nikita
  • 869
  • 1
  • 12
  • 30