4
\$\begingroup\$

I'm creating the admin portion of a project. Specifically, I am wanting to allow the users to update the look-up tables for their areas of interest.

All of my look-up tables have the same structure:

TypeX

Id int
Description string
Active DateTime
Inactive Nullable<DateTime>
CreatedBy string
CreatedDate DateTime
ModifiedBy string
ModifiedDate DateTime

I created a model that holds IEnumerable of each of the look-up tables and a TypeGeneric, which is the same structure but it adds a string gType (which will come into play in a moment). The types are all ITypeModel.

Model

public class TypeModels
{
    public IEnumerable<Type1> Type1 { get; set; }

    public IEnumerable<Type2> Type2 { get; set; }

    public IEnumerable<Type3> Type3 { get; set; }

    public IEnumerable<Type4> Type4 { get; set; }

    public TypeGeneric TypeG { get; set; }
}

Through several failed attempts and learning some odd behavior of Controllers and Actions I finally was able to create "Generic" Action set that would allow me to handle all of my Types. However, it feels more of a hack then an actual solution. Is there is a better way of handling this?

Controller

    public ActionResult Index()
    {
        TypeModels model = new TypeModels();

        model.Type1 = db.Type1.ToList();
        model.Type2 = db.Type2.ToList();
        model.Type3 = db.Type3.ToList();
        model.Type4 = db.Type4.ToList();

        return View(model);
    }

    public ActionResult Create(string type)
    {
        TypeModels m = new TypeModels();
        m.TypeG = new TypeGeneric();
        m.TypeG.sType = type;

        createTypeView(m.TypeG);
        return View(m);
    }

    [HttpPost]
    public ActionResult Create(TypeModels m)
    {
        createType(m.TypeG);

        return RedirectToAction("Index");

    }

    public void createTypeView<T>(T type) where T:ITypeModel
    {
        type.DateActive = DateTime.Now;
    }

    public void createType(TypeGeneric m)
    {
        ITypeModel model = null;
        model = castAndLoadITypeModel(model, m);

        if (ModelState.IsValid)
        {
            DateTime now = DateTime.Now;
            string user = User.Identity.GetUserId();
            model.CreatedDate = model.ModifiedDate = now;
            model.CreatedBy = model.ModifiedBy = user;

            if (model.DateActive < DateTime.Now)
            {
                model.DateActive = now;
            }

            db.Set(model.GetType()).Add(model);
            db.SaveChanges();
        }
    }

    public ITypeModel castAndLoadITypeModel(ITypeModel model, TypeGeneric type)
    {
        switch (type.sType)
        {
            case "1":
                model = new Type1();
                goto case "all";
            case "2":
                model = new Type2();
                goto case "all";
            case "3":
                model = new Type3();
                goto case "all";
            case "4":
                model = new Type4();
                goto case "all";
            case "all":
                loadType(model, type);
                break;
        }
        return model;
    }

    public void loadType(ITypeModel model, TypeGeneric m)
    {
        model.Description = m.Description;
        model.DateActive = m.DateActive;
    }

Index View

@model TMS.Models.TypeModels
@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@using (Html.BeginForm())
{
    <div class="row">
        <div class="col-md-4 col-sm-6">
            <h3>Event Types</h3>
            <div>
                @{ WebGrid grid1 = new WebGrid(Model.Type1);}
                @grid1.GetHtml(
                    tableStyle: "webgrid-table",
                    headerStyle: "webgrid-header",
                    footerStyle: "webgrid-footer",
                    alternatingRowStyle: "webgrid-alternating-row",
                    selectedRowStyle: "webrid-selected-row",
                    rowStyle: "webgrid-row-style",
                    mode: WebGridPagerModes.All,
                    columns: grid1.Columns(
                    grid1.Column("Description", "Types"),
                    grid1.Column("DateActive", "Active"),
                    grid1.Column("DateInactive", "Inactive")
                    ))
            </div>
            @Html.ActionLink("Add", "Create", new { type = "1" })
    </div>

    <div class="col-md-4 col-sm-1"></div>

    <div class="col-md-4 col-sm-5">
        <h3>Dose Amounts</h3>
        <div>
            @{
                WebGrid grid2 = new WebGrid(Model.Type2);
            }
            @grid2.GetHtml(
            tableStyle: "webgrid-table",
            headerStyle: "webgrid-header",
            footerStyle: "webgrid-footer",
            alternatingRowStyle: "webgrid-alternating-row",
            selectedRowStyle: "webrid-selected-row",
            rowStyle: "webgrid-row-style",
            mode: WebGridPagerModes.All,
            columns: grid2.Columns(
            grid2.Column("Description")
            ))
        </div>
        @Html.ActionLink("Add", "Create", new { type = "2" })

    </div>
</div>
}

Create View

@model TMS.Models.TypeModels
@{
    ViewBag.Title = "Create " + Model;
}

<h2>Create @Model.TypeG.sType</h2>

@using (Html.BeginForm())
{
    <div class="form-group">
        @Html.LabelFor(m => m.TypeG.Description, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(m => m.TypeG.Description, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(m => m.TypeG.Description, "*", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(m => m.TypeG.DateActive, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(m => m.TypeG.DateActive, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(m => m.TypeG.DateActive, "*", new { @class = "text-danger" })
        </div>
    </div>
        @Html.EditorFor(m => m.TypeG.sType, new { htmlAttributes = new { @class = "hide" } })
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Create" class="btn btn-default" />
        </div>
    </div>
}

When the add button is clicked on the Index Page, it sends along a string value that tells me what Type was selected. I create and add a GenericType to my model and pass it to the Create View. The data is filled into this GenericType and when it is submitted I grab the string value and assign an ITypeModel the proper TypeClass and fill in the data using my GenericType values.

This solution works, but I have a sneak suspicion that there are better ways of handling this.

Searching online I found a few different ways of handling this, but most of those came from my failures. One was creating my own Controllers for generic Actions, which is really more work than I think its worth.

My other problem was attempting to use an Interface in my model, which the controller doesn't like at all.

\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

I would handle it via a generic base controller:

public abstract class TypeController<T> : Controller
    where T : ITypeModel, class, new()
{
    public ActionResult Create()
    {
        var type = new T();
        return View(type);
    }

    public ActionResult Create(T type)
    {
        if (ModelState.IsValid)
        {
            DateTime now = DateTime.Now;
            string user = User.Identity.GetUserId();
            type.CreatedDate = type.ModifiedDate = now;
            type.CreatedBy = type.ModifiedBy = user;

            if (type.DateActive < DateTime.Now)
            {
                type.DateActive = now;
            }

            db.Set<T>().Add(type);
            db.SaveChanges();

            return RedirectToAction("Index");
        }

        return View(type);
    }

    ...
}

Then, subclass this controller for each type:

public class Type1Controller : TypeController<Type1>
{
}

Then, just link directly to the type being created:

@Html.ActionLink("Create new Type 1", "Create", "Type1")
\$\endgroup\$
2
  • \$\begingroup\$ Would I have to make a controller for each Type? \$\endgroup\$
    – EZE
    Commented Jan 8, 2015 at 21:39
  • \$\begingroup\$ Yes, this serves two purposes: 1) satisfying the type parameter for the generic class and 2) giving you a unique endpoint for routing. However, unless you need anything additional, the type-specific controller can be pretty much empty, as in the sample code above. \$\endgroup\$ Commented Jan 9, 2015 at 15:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.