I have person model class defined as:
public class PersonModel
{
public bool SelectionSubmitted = false;
public bool ShowValidationSummary = false;
public string Name;
public string Get()
{
//actual implementation return some value from the db
return string.Empty;
}
}
The controller implementation is as follows:
class HomeController : Controller
{
[HttpGet]
public ActionResult Index(PersonModel model)
{
if (model.SelectionSubmitted && !ValidateSelections(model))
{
model.ShowValidationSummary = true;
}
return View("Index", model.Get());
}
private bool ValidateSelections(PersonModel model)
{
if(model.Name == "")
{
ModelState.AddModelError("EmptyPersonName", "Person name cannot be null");
}
return ModelState.IsValid;
}
}
The test class and method is defined as:
[TestClass]
public class ChildWithoutPlacementControllerTest
{
private readonly Mock<PersonModel> _mockPersonModel;
public ChildWithoutPlacementControllerTest()
{
_mockPersonModel = new Mock<PersonModel>();
}
[TestMethod]
public void GivenPerson_WhenSearchingForFutureBirthDate_ThenValidationMessageShouldBeShown()
{
//Arrange
HomeController controller = new HomeController();
_mockPersonModel.Setup(x => x.Get()).Returns(It.IsAny<string>());
_mockPersonModel.SetupGet(x => x.Name).Returns(string.Empty);
_mockPersonModel.SetupGet(x => x.SelectionSubmitted).Returns(true);
//Act
controller.Index(_mockPersonModel.Object);
//Assert
var isShowSummarySetToTrue = _mockPersonModel.Object.ShowValidationSummary;
Assert.IsTrue(isShowSummarySetToTrue);
}
}
What I want to achieve is mock the SelectionSubmitted and Name property to true and string.Empty respectively also Setup the Get method of PersonModel class, and check if the test return object has ShowValidationSummary set to true.
However, I am getting that I can't set up the non-virtual property Name.
Am I doing something wrong or is there any way to do it without changing the implementation code?
Am I doing something wrong
This appears to be an XY problem.
is there any way to do it without changing the implementation code
There really is no need for moq in this scenario. You can use inheritance to craft a fake model to be used in the test. The fake model will override the method that is tightly coupled to the database. (more on that later)
public class FakePerson : PersonModel {
public new string Get() {
return string.Empty; //Not calling the base Get
}
}
The test can then be refactored to use the fake model and be exercised to completion as intended.
[TestMethod]
public void GivenPerson_WhenSearchingForFutureBirthDate_ThenValidationMessageShouldBeShown() {
//Arrange
var fakePersonModel = new FakePerson() {
Name = string.Empty,
SelectionSubmitted = true
};
var controller = new HomeController();
//Act
controller.Index(fakePersonModel);
//Assert
var isShowSummarySetToTrue = fakePersonModel.ShowValidationSummary;
Assert.IsTrue(isShowSummarySetToTrue);
}
That aside, your model appears to be doing to much if the actual Get implementation does as you stated here
actual implementation return some value from the db
Consider refactoring that functionality out into a service (Single Responsibility Principle / Separation of Concerns)
public interface IPersonModelService {
string Get(PersonModel person);
}
public class PersonModelService : IPersonModelService {
public string Get(PersonModel person) {
//actual implementation return some value from the db
}
}
and keep the model as lean as possible. Also consider refactoring those public fields into public properties.
public class PersonModel {
public bool SelectionSubmitted { get; set; }
public bool ShowValidationSummary { get; set; }
public string Name { get; set; }
}
The controller would depend on the service abstraction
class HomeController : Controller {
private IPersonModelService service;
public HomeController(IPersonModelService service) {
this.service = service;
}
[HttpGet]
public ActionResult Index(PersonModel model) {
if (model.SelectionSubmitted && !ValidateSelections(model)) {
model.ShowValidationSummary = true;
}
return View("Index", service.Get(model));
}
private bool ValidateSelections(PersonModel model) {
if (model.Name == "") {
ModelState.AddModelError("EmptyPersonName", "Person name cannot be null");
}
return ModelState.IsValid;
}
}
And now the test can be exercised to completion in isolation.
[TestMethod]
public void GivenPerson_WhenSearchingForFutureBirthDate_ThenValidationMessageShouldBeShown() {
//Arrange
var model = new PersonModel() {
Name = string.Empty,
SelectionSubmitted = true
};
var serviceMock = new Mock<IPersonModelService>();
serviceMock.Setup(_ => _.Get(It.IsAny<PersonModel>())).Returns(string.Empty);
var controller = new HomeController(serviceMock.Object);
//Act
controller.Index(model);
//Assert
var isShowSummarySetToTrue = model.ShowValidationSummary;
Assert.IsTrue(isShowSummarySetToTrue);
}
Related
I tried to write unit test for getMark() and faced problem with Moq, with which I'm not familiar. I have no idea what method and object properly mock in order to unit test getMark()
Here is my MarkServiceClass containing getMark()
public class MarkService : IMarkService
{
IMarkService _markService;
IStdService _stdService;
IStdService _stdMService;
RClass cs;
public MarkService(IMarkService markService, IStdService stdService, IStdService stdMService)
{
_markService = markService;
_stdService = stdService;
_stdMService = stdMService;
}
public bool Init(int sID, string pID, string year)
{
try
{
cs = new RClass ();
cs.sLevel = __stdService.GetAsIQueryable().FirstOrDefault(x => x.UID == pID);
var mInfo = __stdMService.GetSTDM(sID, pID, year);
cs.Type = mInfo.CalculateAmount;
return true;
}
catch
{
return false;
}
}
public MarkVM getMark(int sID, string pID, string year)
{
var output=Init(sID, pID, year);
if (!output)
return null;
int sGrade= 0;
int sMark= 0;
//here are conditions where sGrade and sMark used
return new MarkVM
{
Grade = sGrade,
Mark = sMark
};
}
}
and MarkVM
public class MarkVM
{
public int Grade { get; set; }
public int Mark { get; set; }
}
The code you shared is not complete so I had to make some assumptions to give you an example how to unit test getMark
public class MarkVM
{
public int Grade { get; set; }
public int Mark { get; set; }
}
Not knowing what RClass is, I define it with minimal requirements
public class RClass {
public String Uid { get; set; }
public string sLevel { get; set; }
public int Type { get; set; }
}
Same for this Info your service retrieves with GetSTDM
public class Info
{
public int CalculateAmount { get; set; }
}
Now come the interfaces. This is definitely required if you want to mock
public interface IStdService
{
List<RClass> GetAsIQueryable();
Info GetSTDM(int sID, string pID, string year);
}
Those 2 methods are the ones you'll want to mock if you unit test getMark.
Mocking getMark itself will only allow you to check it is called, but not its behavior which is the purpose of unit testing.
Now the main class. I removed the injection of IMarkService in the constructor because I really don't see why you would do that: Markservice implements IMarkService here.
For any reason you use 2 instances of IStdService, I kelpt that but then you need to inject it too.
public class MarkService : IMarkService
{
private IStdService __stdService;
private IStdService __stdMService;
public RClass cs;
public MarkService(IStdService stdMService, IStdService stdService)
{
__stdMService = stdMService;
__stdService = stdService;
}
public bool Init(int sID, string pID, string year)
{
try
{
cs = new RClass();
cs.sLevel = __stdService.GetAsIQueryable().FirstOrDefault(x => x.Uid == pID).sLevel;
var mInfo = __stdMService.GetSTDM(sID, pID, year);
cs.Type = mInfo.CalculateAmount;
return true;
}
catch
{
return false;
}
}
public MarkVM getMark(int sID, string pID, string year)
{
var output = Init(sID, pID, year);
if (!output)
return null;
int sGrade = 0;
int sMark = 0;
//here are conditions where sGrade and sMark used
return new MarkVM
{
Grade = sGrade,
Mark = sMark
};
}
}
Now comes the test. If you want to unit test getMark you could either mock Init from IMarkService, or consider the behavior comes from this Init and then you want to mock GetAsIQueryable and GetSTDM.
I made the assumption second option is what you want.
using System.Collections.Generic;
using MarkServiceNS;
using Moq;// Moq framework where you'll find everything you need
using NUnit.Framework;// Using NUnit for unit test. Because I like it :-)
using NUnit.Framework.Constraints;
namespace UnitTestWithMoqExample
{
public class Tests
{
[SetUp]
public void Setup()
{
}
[Test]
public void getMark()
{
var mockedStdService = new Mock<IStdService>();
mockedStdService.Setup(x => x.GetAsIQueryable())
.Returns(new List<RClass> { new RClass { Uid = "uid", sLevel = "expected", Type = 1 } }); // Here you define what it the mocked result of GetAsIQueryable call.
var mockedStdMService = new Mock<IStdService>();
mockedStdMService.Setup(x => x.GetSTDM(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(new Info { CalculateAmount = 1 });// Same here. You mock GetSTDM. The method parameters are not expected to change the behavior in my unit test, this is why I consider It.Any<T> so whatever you pass to the mock, the result will be the same.
// Here is the assertion. This should do the job
var service = new MarkServiceNS.MarkService(mockedStdMService.Object, mockedStdService.Object);
Assert.IsNotNull(service.getMark(1, "", ""));
Assert.IsInstanceOf(typeof(MarkVM), service.getMark(1, "", ""));
Assert.AreEqual(0, service.getMark(1, "", "").Grade);
Assert.AreEqual(0, service.getMark(1, "", "").Mark);
}
}
}
A basic Moq coding will be like this
[Test]
public void Test1()
{
var mock = new Mock<IMarkService>();
mock.Setup(p => p.getMark(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>())).Returns(new MarkVM());
mock.Verify(p => p.getMark(1001, "P001", "2022"), Times.Once());
}
I am posting this as an example as I don't have your full code
Use the above technique to moq your methods GetAsIQueryable and GetSTDM and CalculateAmount
And call the method Init and then call the getMark
I am trying to unit test a method within my controller in my Web API using XUnit. The role of the method is to get a single title, by ISBN, from the database. The issue I came across during unit testing is that I am unsure how to insert the dummy data that I must perform the test on, as well as how the Assert function works.
TitleController.cs
[ApiController]
[Route("titlecontroller")]
public class TitleController : Controller
{
private IGtlTitleRepository _gtlTitleRepository;
public TitleController(IGtlTitleRepository gtlTitleRepository)
{
_gtlTitleRepository = gtlTitleRepository;
}
[Route("getTitle/{ISBN}")]
[HttpGet()]
public GtlTitle GetTitle(string ISBN)
{
return _gtlTitleRepository.GetTitle(ISBN);
}
}
IGtlTitleRepository.cs
public interface IGtlTitleRepository
{
GtlTitle GetTitle(string ISBN);
}
MockGtlTitleRepository.cs
public class MockGtlTitleRepository : IGtlTitleRepository
{
private readonly string _connection;
public MockGtlTitleRepository(IOptions<ConnectionStringList> connectionStrings)
{
_connection = connectionStrings.Value.GTLDatabase;
}
private List<GtlTitle> _titleList;
public GtlTitle GetTitle(string ISBN)
{
using (var connection = new SqlConnection(_connection))
{
connection.Open();
return connection.QuerySingle<GtlTitle>("GetTitleByISBN", new { ISBN }, commandType: CommandType.StoredProcedure);
}
}
}
Right, as for my test code, I was able to write the following code, but as I said above, I can't figure out a proper way to test the method.
public class UnitTest1
{
[Fact]
public void Test1()
{
var repositoryMock = new Mock<IGtlTitleRepository>();
var title = new GtlTitle();
repositoryMock.Setup(r => r.GetTitle("978-0-10074-5")).Returns(title);
var controller = new TitleController(repositoryMock.Object);
var result = controller.GetTitle("978-0-10074-5");
// assert??
repositoryMock.VerifyAll();
}
}
What should be done within this unit test in order to properly test the method?
EDIT:
GtlTitle.cs
public class GtlTitle
{
public string ISBN { get; set; }
public string VolumeName { get; set; }
public string TitleDescription { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PublisherName { get; set; }
}
Before going to testing, there are a few things I recommend updating in your code:
Make your repository methods and controller actions async (thus web server can process requests while waiting for database roundtrips for previous calls)
Use ActionResult as an action return type. This way you can send different http status codes to the client.
Return 404 NotFound status code when title not found instead of returning successful result with null as payload.
Consider using a RESTful approach for API endpoints. E.g. base uri for titles resource should be something like api/titles
Don't specify getTitle for getting title endpoint, because you know HTTP verb which endpoint is mapped to (GET) and base resource url (api/titles).
With these notes applied:
[ApiController]
[Route("api/titles")]
public class TitleController : Controller
{
private IGtlTitleRepository _gtlTitleRepository;
public TitleController(IGtlTitleRepository gtlTitleRepository)
{
_gtlTitleRepository = gtlTitleRepository;
}
[HttpGet("{ISBN}")] // GET api/titles/{ISBN}
public async Task<ActionResult<GtlTitle>> GetTitle(string ISBN)
{
var title = await _gtlTitleRepository.GetTitle(ISBN);
if (title == null)
return NotFound();
return title;
}
}
Testing successful title retrieving:
[Fact]
public async Task Should_Return_Title_When_Title_Found()
{
var repositoryMock = new Mock<IGtlTitleRepository>();
var title = new GtlTitle();
repositoryMock.Setup(r => r.Get("978-0-10074-5")).Returns(Task.FromResult(title));
var controller = new TitleController(repositoryMock.Object);
var result = await controller.GetTitle("978-0-10074-5");
Assert.Equal(title, result.Value);
}
When title not found:
[Fact]
public async Task Should_Return_404_When_Title_Not_Found()
{
var repositoryMock = new Mock<IGtlTitleRepository>();
repositoryMock.Setup(r => r.Get("978-0-10074-5")).Returns(Task.FromResult<GtlTitle>(null));
var controller = new TitleController(repositoryMock.Object);
var result = await controller.GetTitle("978-0-10074-5");
Assert.IsType<NotFoundResult>(result.Result);
}
public class MyUser: IIdentity, IMyUser{
// ommited for abbrev.
}
public interface IMyUser
{
int Id { get; set; }
int? CompanyId { get; set; }
}
inside MyController I'm using MyUser besides others to populate
comboboxes
public ActionResult Details(int? subsidId = null, int? req = null)
{
...
MyUser user = this.User.GetInfo();
var obj1 = ... // ommited on purpose for abbrev.
populateCombos(subsidId, user.CompanyId, req);
}
I'm getting exception on this line populateCombos cause user object is always null.
Inside same controller I'm injecting interface which IMyUser implements
[Inject]
public IMyUser MyUser { get; set; }
this property is correctly binded using ninject (like others in my app)
kernel.Bind<IMyUser>().To<MyUser>().InRequestScope();
now on testing project I'm initializing controller with mocking requested dependencies
[SetUp]
public void Setup()
{
_controller = new MyController(){
... repositories....
MyUser = MockMyUser()
}
}
private IMyUser MockMyUser()
{
var u = new Mock<IMyUser>();
u.SetupGet(x => x.Id).Returns(1);
u.SetupGet(x => x.CompanyId).Returns(99);
return u.Object;
}
and inside test method I wrote simple test
[Test]
public void CanDoDetails()
{
ViewResult res = this.controller.Details(1, 2) as ViewResult;
var model = result.Model as MyModel;
Assert.IsNotNull(model);
}
Question is:
why I'm getting this dependency (MyUser inside MyController as
null) cause it's injected properly? What I'm doing wrong?
Update:
public static MyUser GetInfo(this IPrincipal principal)
{
if (principal != null)
{
return principal.Identity as MyUser;
}
return null;
}
Update 2:
Based on Nkosi answer bellow I make following changes
public interface IMyUser : IIdentity { ... }
and inside Details ActionMethod controller
IMyUser user = this.User.GetInfo();
and in test method under
[SetUp]
public void Setup()
{
var mockUser = MockMyUser();
string[] roles = new[] { "Admin", "SuperUser" };
_controller = new MyController()
{
....
MyUser = this.MockMyUser(),
ControllerContext = new ControllerContext() {
Controller = _controller,
RequestContext = new RequestContext(new MockHttpContext(mockUser, roles), new RouteData())
}
}
}
but I'm still getting `IMyUser user = this.User.GetInfo();`
`this.User` is still null.
p.s. I also changed GetInfo to return IMyUser instead of MyUser.
What about mocking controller also?
Mock<MyController> mockController = new Mock<MyController>();
mockController.SetupGet(t => t.MyUser).Returns(MockMyUser());
then you can access your controller via mockController.Object and try your test.
My advice would also be to make IMyUser inherit from IIdentity
public class MyUser: IMyUser {
// ommited for abbrev.
}
public interface IMyUser: IIdentity {
int Id { get; set; }
int? CompanyId { get; set; }
}
For your unit test to work if you are using the Controller.User { get; } is to create a mock/Fake user for the controller. How ever in order to get access to the User, which is read-only, you have to create a mock HttpContext. yuck. Luckily you only want access to the User anyway.
private class MockHttpContext : HttpContextBase {
private readonly IPrincipal user;
public MockHttpContext(IIdentity identity , string[] roles = null) {
var principal = new GenericPrincipal(identity, roles ?? new string[] { });
user = principal;
}
public override IPrincipal User {
get {
return user;
}
set {
base.User = value;
}
}
}
You can setup the principal to suit your authentication setup with what ever claims you apply at runtime. This is just an example.
[SetUp]
public void Setup()
{
string[] roles = new[] { "Admin", "SuperUser" };
var mockUser = MockMyUser();
_controller = new MyController(){
... repositories....
MyUser = mockUser
};
_controller.ControllerContext = new ControllerContext() {
Controller = _controller,
RequestContext = new RequestContext(new MockHttpContext(mockUser, roles), new RouteData())
};
}
private IMyUser MockMyUser()
{
var u = new Mock<IMyUser>();
u.Setup(x => x.Name).Returns("username#test.io");
u.Setup(x => x.Id).Returns(1);
u.Setup(x => x.CompanyId).Returns(99);
return u.Object;
}
This should now allow
public static IMyUser GetInfo(this IPrincipal principal) {
if (principal != null) {
return principal.Identity as IMyUser;
}
return null;
}
To return the principal.Identity as IMyUser to not be null.
UPDATE
I recreated a minimal version of your test using what I provided you above and was able to test it and pass.
[TestClass]
public class MyUserDependentControllerTest {
[TestMethod]
public void CanDoDetails() {
//Arrange
string[] roles = new[] { "Admin", "SuperUser" };
var u = new Mock<IMyUser>();
u.Setup(x => x.Name).Returns("username#test.io");
u.Setup(x => x.Id).Returns(1);
u.Setup(x => x.CompanyId).Returns(99).Verifiable();
var mockUser = u.Object;
var controller = new MyController() {
//... repositories....
MyUser = mockUser
};
controller.ControllerContext = new ControllerContext() {
Controller = controller,
RequestContext = new RequestContext(new MockHttpContext(mockUser, roles), new RouteData())
};
//Act
var result = controller.Details(1, 2);
//Assert
var viewResult = result as ViewResult;
Assert.IsNotNull(viewResult);
var model = viewResult.Model as MyModel;
Assert.IsNotNull(model);
u.Verify();
}
public class MyController : Controller {
public ActionResult Details(int? subsidId = null, int? req = null) {
//...
var user = this.User.GetInfo();
//
populateCombos(subsidId, user.CompanyId, req);
//this is just for matching test expectations
var model = new MyModel();
return View(model);
}
private void populateCombos(int? subsidId, int? nullable, int? req) {
//Empty as I have no clue what happens in here
}
public IMyUser MyUser { get; set; }
}
public class MyModel { }
}
I have the following model
public class Dog
{
public string NickName { get; set; }
public int Color { get; set; }
}
and I have the following api controller method which is exposed through an API
public class DogController : ApiController
{
// GET /v1/dogs
public IEnumerable<string> Get([FromUri] Dog dog)
{ ...}
Now, I would like to issue the GET request as follows:
GET http://localhost:90000/v1/dogs?nick_name=Fido&color=1
Question: How do I bind the query string parameter nick_name to property NickName in the dog class? I know I can call the API without the underscore (i.e. nickname) or change NickName to Nick_Name and get the value, but I need the names to remain like that for convention.
Edit
This question is not a duplicate because it is about ASP.NET WebApi not ASP.NET MVC 2
Implementing the IModelBinder,
public class DogModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(Dog))
{
return false;
}
var model = (Dog)bindingContext.Model ?? new Dog();
var hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName);
var searchPrefix = (hasPrefix) ? bindingContext.ModelName + "." : "";
model.NickName = GetValue(bindingContext, searchPrefix, "nick_name");
int colorId = 0;
if (int.TryParse(GetValue(bindingContext, searchPrefix, "colour"), out colorId))
{
model.Color = colorId; // <1>
}
bindingContext.Model = model;
return true;
}
private string GetValue(ModelBindingContext context, string prefix, string key)
{
var result = context.ValueProvider.GetValue(prefix + key); // <4>
return result == null ? null : result.AttemptedValue;
}
}
And Create ModelBinderProvider,
public class DogModelBinderProvider : ModelBinderProvider
{
private CollectionModelBinderProvider originalProvider = null;
public DogModelBinderProvider(CollectionModelBinderProvider originalProvider)
{
this.originalProvider = originalProvider;
}
public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
{
// get the default implementation of provider for handling collections
IModelBinder originalBinder = originalProvider.GetBinder(configuration, modelType);
if (originalBinder != null)
{
return new DogModelBinder();
}
return null;
}
}
and using in controller something like,
public IEnumerable<string> Get([ModelBinder(typeof(DogModelBinder))] Dog dog)
{
//controller logic
}
I have the following abstract class:
public abstract class TemplateBase
{
public abstract string TemplateName { get; }
public string RuntimeTypeName { get { return GetType().FullName; } }
public abstract List<AreaContainer> TemplateAreas { get; }
}
then these 2 inherited classes:
public class SingleColumnTemplate : TemplateBase
{
public override string TemplateName { get { return "Single column"; } }
public AreaContainer CenterColumn { get; private set; }
public SingleColumnTemplate()
{
this.CenterColumn = new AreaContainer("Middle");
}
private List<AreaContainer> templateAreas;
public override List<AreaContainer> TemplateAreas
{
get
{
if (this.templateAreas == null)
{
this.templateAreas = new List<AreaContainer>() { this.CenterColumn };
}
return this.templateAreas;
}
}
}
and
public class TwoColumnTemplate : TemplateBase
{
public override string TemplateName { get { return "Two column"; } }
public AreaContainer LeftColumn { get; private set; }
public AreaContainer RightColumn { get; private set; }
public TwoColumnTemplate()
{
LeftColumn = new AreaContainer("Left");
RightColumn = new AreaContainer("Right");
}
private List<AreaContainer> templateAreas;
public override List<AreaContainer> TemplateAreas
{
get
{
if (this.templateAreas == null)
{
this.templateAreas = new List<AreaContainer>() { this.LeftColumn, this.RightColumn };
}
return this.templateAreas;
}
}
}
I also have this class that is my model for editing:
public class ContentPage
{
public virtual int ContentPageId { get; set; }
public virtual string Title { get; set; }
public TemplateBase Template { get; set; }
}
Question:
for my ActionResults I have the following:
[HttpGet]
public ActionResult Edit()
{
var row = new ContentPage();
var template = new TwoColumnTemplate();
// Areas
HtmlArea html_left = new HtmlArea();
html_left.HtmlContent = "left area html content";
HtmlArea html_right = new HtmlArea();
html_right.HtmlContent = "right area html content";
template.LeftColumn.Areas.Add(html_left);
template.RightColumn.Areas.Add(html_right);
row.Template = template;
return View(row);
}
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(ContentPage row)
{
// Here i could loop through List -TemplateAreas and save each template Area to Db. I guess that would work
return this.View(row);
}
Question:
For HttpGet- how would I load row Template from the database? since it could be SingleColumnClass or TwoColumnClass.
how would my ViewModel look like to solve this?
thanks
You can write your own Model Binder that is responsible for binding TemplateBase. You will still need to have a way of knowing (in the model binder) which type you will be using a runtime, but you can always delegate that to a factory or service locator of some sort. I did a quick google search and here is a blog post I found that gives you some information for making a model binder for a similar scenario:
http://weblogs.asp.net/bhaskarghosh/archive/2009/07/08/7143564.aspx
EDIT: The blog leaves out how you tell MVC about your model binder. When the application starts, you can add your model binder to System.Web.Mvc.ModelBinders.Binders
HTH
You need to know the template type in you controller, so you can pass a parameter from the view to the controller, indicating the type (SingleColumn or TwoColumn). You could do this witn a Enum:
public enum TemplateType
{
SingleColumn,
TwoColumn
}
[HttpGet]
public ActionResult Edit(TemplateType templateType)
{
var row = new ContentPage();
TemplateBase template;
if (templateType == TemplateType.SingleColumn)
{
template = new SingleColumnTemplate();
}
else
{
template = new TwoColumnTemplate();
}
...
return View(row);
}
When you create the action link from your view you can specify:
<%= Html.ActionLink("Edit",
"Edit",
"YouController",
new
{
// singlecolumn or twocolumn
// depending on your concrete view
TemplateType = TemplateType.xxx
},
null);
I wonder if you could do something like this?
[HttpGet]
public ActionResult Edit(TemplateType templateType)
{
var row = new ContentPage();
TemplateBase template = (TemplateBase)Activator.CreateInstance(templateType);
...
return View(row);
}
templateType would have to be the exact name of your inherited classes (you can ignore case)