Most of the code I see online on MVC3 has very little code in the controller, but I can't seem to figure out how to make this code more streamlined.
Maybe if you take a look at it you can suggest improvements. If you need to see my UserModel class, let me know.
Here's the code for the Account controller.
namespace WebUI.Controllers
{
public class AccountController : Controller
{
public ActionResult Register()
{
UserModel model = new UserModel();
EFCityRepository cityRepo = new EFCityRepository();
model.Cities = new List<SelectListItem>();
foreach (var city in cityRepo.FindAllCities()) {
model.Cities.Add(new SelectListItem { Text = city.Name, Value = city.CityId.ToString(), Selected = true });
}
EFGenderRepository genderRepo = new EFGenderRepository();
model.Genders = new List<SelectListItem>();
foreach (var gender in genderRepo.FindAllGenders()) {
model.Genders.Add(new SelectListItem { Text = gender.Name, Value = gender.GenderId.ToString(), Selected = true });
}
return View(model);
}
[HttpPost]
public ActionResult Register(UserModel model)
{
EFCityRepository cityRepo = new EFCityRepository();
model.Cities = new List<SelectListItem>();
foreach (var city in cityRepo.FindAllCities())
{
model.Cities.Add(new SelectListItem { Text = city.Name, Value = city.CityId.ToString(), Selected = true });
}
EFGenderRepository genderRepo = new EFGenderRepository();
model.Genders = new List<SelectListItem>();
foreach (var gender in genderRepo.FindAllGenders())
{
model.Genders.Add(new SelectListItem { Text = gender.Name, Value = gender.GenderId.ToString(), Selected = true });
}
if (ModelState.IsValid)
{
Domain.User user = new Domain.User();
user.UserRoleId = 1;
user.Nickname = model.Nickname;
user.Name = model.Name;
user.Lastname = model.Lastname;
user.GenderId = model.GenderId;
user.Address = model.Address;
user.Email = model.Email;
user.Telephone = model.Telephone;
user.MobilePhone = model.MobilePhone;
user.Carnet = model.Carnet;
user.DateOfBirth = model.DateOfBirth;
user.DateOfRegistry = DateTime.Now;
user.LastDateLogin = DateTime.Now;
user.IsActive = false;
user.LanceCreditBalance = 5;
user.LancesSpent = 0;
user.Login = model.Login;
user.Password = model.Password;
user.EmailVerificationCode = "TempTokenString";
user.CityId = model.CityId;
EFUserRepository repo = new EFUserRepository();
var result = repo.CreateUser(user);
if (result == UserCreationResults.Ok)
{
FormsAuthentication.SetAuthCookie(model.Nickname, false /* createPersistentCookie */);
return RedirectToAction("Index", "Home");
}
else
{
switch (result)
{
case UserCreationResults.UsernameExists:
ModelState.AddModelError("", "El nombre de usuario ya esta siendo utilizado.");
break;
case UserCreationResults.EmailAlreadyExists:
ModelState.AddModelError("", "Ese correo ya esta en uso.");
break;
case UserCreationResults.NicknameAlreadyExists:
ModelState.AddModelError("", "El nickname ya esta siendo utilizado.");
break;
case UserCreationResults.UnknownError:
ModelState.AddModelError("", "Algo durante el registro. Por favor intente de nuevo.");
break;
default:
break;
}
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
}
I'm using Entity Framework as my ORM, it generates a User class automatically for me. However, I made a User*Model* class so I could add data annotations for the views to use. Maybe this is the wrong idea?
I have many suggestions. For startes, read up about Dependancy Injection and Inversion of Control (DI and IoC). They will make all that boilerplate object instantiation a thing of the past.
Next, convert those for-each list builders into Linq expressions. Much more succinct and more likely faster as well.
Then, in your post handler, again do the same things there. In addition, get to know AutoMapper, which will automatically map your view to domain classes and make your life much easier.
If you did those things, your code would be reduced by 2x, maybe even 3.
EDIT:
An example linq query, because I really don't know the definition of your objects... would look something like this:
model.Cities = cityRepo.FindAllCities().Select(city => new SelectListItem() {
Text = city.Name, Value = city.CityId.ToString()}).ToList();
Notice how you don't have to new up a new List, since that's returned by the ToList() method. It is also using projection to select the items into a new SelectListItem.
Basically, you could write your method like this, using Dependancy Injection, Linq, and AutoMapper (it looks longer because i had to break lines multiple times to fit the small viewing are of SO):
namespace WebUI.Controllers
{
public class AccountController : Controller
{
private IGenderRepository _genderRepo;
private ICityrRepository _cityRepo;
private IUserRepository _userRepo;
public AccountController(IGenderRepository gr, ICityRepository cr,
IUserRepository ur)
{
_genderRepo = gr;
_cityRepo = cr;
_userRepo = ur;
}
public ActionResult Register()
{
UserModel model = new UserModel();
// Selected property is ignored by MVC on SelectListItems
model.Cities = _cityRepo.FindAllCities().Select(city =>
new SelectListItem() { Text = city.Name,
Value = city.CityId.ToString()}).ToList();
model.Genders = _genderRepo.FindAllGenders().Select(gender =>
new SelectListItem() { Text = gender.Name,
Value = gender.GenderId.ToString()}).ToList();
return View(model);
}
[HttpPost]
public ActionResult Register(UserModel model)
{
model.Cities = _cityRepo.FindAllCities().Select(city =>
new SelectListItem() { Text = city.Name,
Value = city.CityId.ToString()}).ToList();
model.Genders = _genderRepo.FindAllGenders().Select(gender =>
new SelectListItem() { Text = gender.Name,
Value = gender.GenderId.ToString()}).ToList();
if (ModelState.IsValid)
{
Domain.User user = Mapper.Map<Domain.User, Model>(model)
var result = _userRepo.CreateUser(user);
if (result == UserCreationResults.Ok) {
FormsAuthentication.SetAuthCookie(model.Nickname, false);
return RedirectToAction("Index", "Home");
} else {
ModelState.AddModelError("", GetErrorString(result));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
}
your Idea is good. Because it is not a good practice to have entity classes as the view models in MVC layer because it will create a tight coupling between your presentation and persistence logic.
To cleanup yuor code you can use AutoMapper ( http://automapper.codeplex.com/ ) to map your entity classes to view model easy without writing too many codes. http://jasona.wordpress.com/2010/02/05/getting-started-with-automapper/ here is a good article for you.
Related
I would like to consume my organizations dynamics oData endpoint but with early bound classes. However, there are a lot of early bound tools out there and I wanted to know which one provides the best developer experience/least resistance?
For example, there is this one:
https://github.com/daryllabar/DLaB.Xrm.XrmToolBoxTools
https://github.com/yagasoft/DynamicsCrm-CodeGenerator
and so on. Is there a developer preference/method out there?
Early bound classes are for use with the Organization Service which is a SOAP service. The normal way to generate those classes is using CrmSvcUtil.
OData can be used in Organization Data Service or Web API, but those don't have Early Bound classes.
Further reading: Introducing the Microsoft Dynamics 365 web services
It's not impossible to use with standard SOAP Early bound class. We just have to be creative. If we work just with basic attributes (fields, not relationships, ecc) it seems possible. For example. for create and update, OData will not accept the entire early bounded class, just pass the attibutes:
class Program
{
static void Main(string[] args)
{
string token = System.Threading.Tasks.Task.Run(() => GetToken()).Result;
CRMWebAPI dynamicsWebAPI = new CRMWebAPI("https:/ORG.api.crm4.dynamics.com/api/data/v9.1/",
token);
CRMGetListOptions listOptions = new CRMGetListOptions
{
Select = new string[] { "EntitySetName" },
Filter = "LogicalName eq 'contact'"
};
dynamic entityDefinitions = dynamicsWebAPI.GetList<ExpandoObject>("EntityDefinitions", listOptions).Result;
Contact contact = new Contact
{
FirstName = "Felipe",
LastName = "Test",
MobilePhone = "38421254"
};
dynamic ret = System.Threading.Tasks.Task.Run(async () => await dynamicsWebAPI.Create(entityDefinitions.List[0].EntitySetName, KeyPairValueToObject(contact.Attributes))).Result;
}
public static async Task<string> GetToken()
{
string api = "https://ORG.api.crm4.dynamics.com/";
ClientCredential credential = new ClientCredential("CLIENT_ID", "CLIENT_SECRET");
AuthenticationContext authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/commom/oauth2/authorize");
return authenticationContext.AcquireTokenAsync(api, credential).Result.AccessToken;
}
public static object KeyPairValueToObject(AttributeCollection keyValuePairs)
{
dynamic expando = new ExpandoObject();
var obj = expando as IDictionary<string, object>;
foreach (var keyValuePair in keyValuePairs)
obj.Add(keyValuePair.Key, keyValuePair.Value);
return obj;
}
}
It's a simple approach and I didn't went further.
Maybe we have to serealize other objects as OptionSets, DateTime (pass just the string) and EntityReferences but this simple test worked fine to me. I'm using Xrm.Tools.WebAPI and Microsoft.IdentityModel.Clients.ActiveDirectory. Maybe it's a way.
[Edit]
And so I decided to go and created a not well tested method to cast the attributes. Problems: We have to follow OData statments to use the API. To update/create an entity reference we can use this to reference https://www.inogic.com/blog/2016/02/set-values-of-all-data-types-using-web-api-in-dynamics-crm/
So
//To EntityReference
entityToUpdateOrCreate["FIELD_SCHEMA_NAME#odata.bind"] = "/ENTITY_SET_NAME(GUID)";
So, it's the Schema name, not field name. If you use CamelCase when set you fields name you'll have a problem where. We can resolve that with a (to that cute) code
public static object EntityToObject<T>(T entity) where T : Entity
{
dynamic expando = new ExpandoObject();
var obj = expando as IDictionary<string, object>;
foreach (var keyValuePair in entity.Attributes)
{
obj.Add(GetFieldName(entity, keyValuePair), CastEntityAttibutesValueOnDynamicObject(keyValuePair.Value));
}
return obj;
}
public static object CastEntityAttibutesValueOnDynamicObject(object attributeValue)
{
if (attributeValue.GetType().Name == "EntityReference")
{
CRMGetListOptions listOptions = new CRMGetListOptions
{
Select = new string[] { "EntitySetName" },
Filter = $"LogicalName eq '{((EntityReference)attributeValue).LogicalName}'"
};
dynamic entitySetName = dynamicsWebAPI.GetList<ExpandoObject>("EntityDefinitions", listOptions).Result.List[0];
return $"/{entitySetName.EntitySetName}({((EntityReference)attributeValue).Id})";
}
else if (attributeValue.GetType().Name == "OptionSetValue")
{
return ((OptionSetValue)attributeValue).Value;
}
else if (attributeValue.GetType().Name == "DateTime")
{
return ((DateTime)attributeValue).ToString("yyyy-MM-dd");
}
else if (attributeValue.GetType().Name == "Money")
{
return ((Money)attributeValue).Value;
}
else if (attributeValue.GetType().Name == "AliasedValue")
{
return CastEntityAttibutesValueOnDynamicObject(((AliasedValue)attributeValue).Value);
}
else
{
return attributeValue;
}
}
public static string GetFieldName<T>(T entity, KeyValuePair<string, object> keyValuePair) where T : Entity
{
switch (keyValuePair.Value.GetType().Name)
{
case "EntityReference":
var entityNameList = System.Threading.Tasks.Task.Run(async () => await dynamicsWebAPI.GetEntityDisplayNameList()).Result;
var firstEntity = entityNameList.Where(x => x.LogicalName == entity.LogicalName).FirstOrDefault();
var attrNameList = System.Threading.Tasks.Task.Run(async () => await dynamicsWebAPI.GetAttributeDisplayNameList(firstEntity.MetadataId)).Result;
return attrNameList.Where(x => x.LogicalName == keyValuePair.Key).Single().SchemaName + "#odata.bind";
case "ActivityParty":
throw new NotImplementedException(); //TODO
default:
return keyValuePair.Key;
}
}
Please, note that this approach do not seems fast or good in anyway. It's better if you have all this values as static so we can save some fetches
[Edit 2]
I just found on XRMToolBox a plugin called "Early bound generator for Web API" and it seems to be the best option. Maybe you should give it a try if you're still curious about that. I guess its the best approach.
The final code is this:
static void Main(string[] args)
{
string token = Task.Run(() => GetToken()).Result;
dynamicsWebAPI = new CRMWebAPI("https://ORG.api.crm4.dynamics.com/api/data/v9.1/",
token);
Contact contact = new Contact
{
FirstName = "Felipe",
LastName = "Test",
MobilePhone = "38421254",
new_Salutation = new EntityReference(new_salutation.EntitySetName, new Guid("{BFA27540-7BB9-E611-80EE-FC15B4281C8C}")),
BirthDate = new DateTime(1993, 04, 14),
};
dynamic ret = Task.Run(async () => await dynamicsWebAPI.Create(Contact.EntitySetName, contact.ToExpandoObject())).Result;
Contact createdContact = dynamicsWebAPI.Get<Contact>(Contact.EntitySetName, ret, new CRMGetListOptions
{
Select = new string[] { "*" }
}).Result;
}
and you have to change the ToExpandoObject on Entity.cs class (generated by the plugin)
public ExpandoObject ToExpandoObject()
{
dynamic expando = new ExpandoObject();
var expandoObject = expando as IDictionary<string, object>;
foreach (var attributes in Attributes)
{
if (attributes.Key == GetIdAttribute())
{
continue;
}
var value = attributes.Value;
var key = attributes.Key;
if (value is EntityReference entityReference)
{
value = $"/{entityReference.EntitySetName}({entityReference.EntityId})";
}
else
{
key = key.ToLower();
if (value is DateTime dateTimeValue)
{
var propertyForAttribute = GetPublicInstanceProperties().FirstOrDefault(x =>
x.Name.Equals(key, StringComparison.InvariantCultureIgnoreCase));
if (propertyForAttribute != null)
{
var onlyDateAttr = propertyForAttribute.GetCustomAttribute<OnlyDateAttribute>();
if (onlyDateAttr != null)
{
value = dateTimeValue.ToString(OnlyDateAttribute.Format);
}
}
}
}
expandoObject.Add(key, value);
}
return (ExpandoObject)expandoObject;
}
Links:
https://github.com/davidyack/Xrm.Tools.CRMWebAPI
https://www.xrmtoolbox.com/plugins/crm.webApi.earlyBoundGenerator/
We currently use XrmToolkit which has it's own version of early binding called ProxyClasses but will allow you to generate early binding using the CRM Service Utility (CrmSvcUtil). It does a lot more than just early binding which is why we use it on all of our projects but the early binding features alone would have me sold on it. in order to regenerate an entity definition all you do is right click the cs file in visual studio and select regenerate and it is done in a few seconds.
For my first 3 years of CRM development I used the XrmToolbox "Early Bound Generator" plugin which is really helpful as well.
The following code is taken from the tutorial: http://www.asp.net/mvc/tutorials/getting-started-with-aspnet-mvc3/cs/examining-the-edit-methods-and-edit-view which shows how ASP.net MVC 3 can be used to manage a movie database.
In the tutoral, a list object is added to the controller class that contains every movie genre that exists in the database. This list is then passed to a drop-down in the view enabling the database to be searched by genre.
Controller: (code related to movie genre in bold)
public ActionResult SearchIndex(string movieGenre, string searchString)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
}
What I want to do is enhance this further so that the movies can be searched by price as well as genre. I know I can re-use the much of the same code to do this. I think I need to create a new class that the controller class can pass either the genre or price. Is this correct? IF so, I'd appreciate an example. Thanks.
Update/Clarification:
I want to avoid repeating the code for both genre and price as below:
public ActionResult SearchIndex(string movieGenre, string searchString,float moviePrice)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var PriceLst = new List<string>();
var PriceQry = from d in db.Movies
orderby d.Genre
select d.Genre;
PriceLst.AddRange(GenreQry.Distinct());
ViewBag.moviePrice = new SelectList(PriceLst);
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
if (string.IsNullOrEmpty(moviePrice))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == moviePrice));
}
}
You just have to insert a text box in the view to get price value. Then receive this value at action and modify the query to get desired results.
like this:
#Html.ActionLink("Create New", "Create")
#using (Html.BeginForm()){
<p>Genre: #Html.DropDownList("movieGenre", "All")
Title: #Html.TextBox("SearchString")
Price: #Html.TextBox("Price")
<input type="submit" value="Filter" /></p>
}
And in the action method you are using the code below to populate the dropdownlist with genre values. You need not do the same for price value.
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
And in your action method you just have to use the value of price to filter data
public ActionResult SearchIndex(string movieGenre, string searchString,float price)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where((x => x.Genre == movieGenre) &&(x => x.Price== price)));
}
}
You can do it in so many different ways while all are correct but it depends on the complexity of your project. Basically you don't want to over-engineer a simple program. But in general you should move all of your logic to a separate class and use your actions for creating and calling the right logic class:
public class GetMoviesRequest
{
public string Name { get; set; }
public float? Price { get; set; }
}
public class MoviesLogic
{
private List<Movie> Movies;
public IEnumerable<Movie> Get(GetMoviesRequest request)
{
IEnumerable<Movie> filtered = Movies.AsQueryable();
if (!string.IsNullOrEmpty(request.Name))
{
//Filter by name
filtered = filtered.Where(m => m.Name == request.Name);
}
if (request.Price.HasValue)
{
//Filter by value
filtered = filtered.Where(m => m.Price == request.Price);
}
return filtered;
}
}
public class MyController
{
public ActionResult SearchIndex(string movieGenre, string searchString)
{
var logic = new MoviesLogic();
var movies = logic.Get(new GetMoviesRequest() { Name = searchString } )
///do stuff with movies
}
}
Hi I am quite new on MVC and I am trying to create a simple conversion from Fahrenheit to Celsius along with its unit testing. Sorry in advance for putting all the code here.
This is my controller code:
public string Convert(double value,string option)
{
string d;
if(option=="1") {
d = " To Celcius"+FahrenheitToCelsius(value).ToString();
}
else {
d = " To Fahrenheit" + CelsiusToFahrenheit(value).ToString();
}
return "ConvertTo" + d;
}
public static double CelsiusToFahrenheit(double temperatureCelsius)
{
double celsius = temperatureCelsius;
return (celsius * 9 / 5) + 32;
}
public static double FahrenheitToCelsius (double temperatureFahrenheit)
{
double fahrenheit = temperatureFahrenheit;
return (fahrenheit - 32) * 5 / 9;
}
This is my View Page
protected void btnConvert(object sender, EventArgs e)
{
if (DropDownList1.SelectedValue=="1"){
double temp = TemperatureConverterController.FahrenheitToCelsius(double.Parse(TextBox1.Text));
Literal1.Text = temp.ToString();
}
else{
double temp = TemperatureConverterController.CelsiusToFahrenheit(double.Parse(TextBox1.Text));
Literal1.Text = temp.ToString();
Literal1.Text = temp.ToString();
}
}
When i do this unit testing i got an error:
[TestMethod]
public void ConvertReturnsAViewResultWhenInputDataIsValid()
{
//Arrange
var controller = new TemperatureConverterController();
//Act
double x = 80;
double y = 25;
var result = controller.Convert(x, "1") as ViewResult;
// here i get this error under ViewResult //
//Assert
Assert.IsInstanceOfType(result, typeof(ViewResult));
}
[TestMethod]
public void ConvertAsksForAViewTemplateNamedConvert()
{
//Arrange
var controller = new TemperatureConverterController();
String expectedViewTemplate = "Convert";
//Act
double x = 80;
double y = 25;
var result = controller.Convert(x, "1") as ViewResult;
////Assert
Assert.AreEqual<String>(expectedViewTemplate, result.ViewName);
}
Error is:
Error Cannot convert type 'string' to 'System.Web.Mvc.ViewResult' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion.
the problem is here
var result = controller.Convert(x, "1") as ViewResult;
your Convert method is returning string and you are casting it as ViewResult
Your convert method should looks like
public ActionResult Convert()
{
//Make a Model class and pass it to View
//...
return View(model_class_object);
}
Alternatively you can make controller like this
public ActionResult Convert()
{
ViewData["tempvalue"]=Convert(x, "1");
//Make a Model class and pass it to View
//...
return View();
}
and on your View you can just print it
#ViewData["tempvalue"].ToString()
In MVC the controller code should return an "ActionResult" object containing the model.
If the data you want to pass to view is simply a string use:
public ActionResult Convert()
{
//...
return View("your result here...");
}
You can refer to data returned by controller using the "Model" property in Views or Tests.
Let's go backwards for a minute here.
Controller
public class ConvertController : Controller
{
public ActionResult Convert(MyConvertViewModel vm)
{
if (vm == null) { return View("convert", new MyConvertViewModel { ShowResult = false }); }
if (vm.Option == 1)
{
vm.Result = FahrenheitToCelsius(vm.Input);
vm.OptionName = "Fahrenheit To Celsius";
}
else
{
vm.Result = CelsiusToFahrenheit(vm.Input);
vm.OptionName = "Celsius to Fahrenheit";
}
vm.ShowResult = true;
//not needed, just for an example
ViewData.Add("glosrob-example", "A value goes here!");
return View("convert", vm);
}
private static double CelsiusToFahrenheit(double temperatureCelsius)
{
double celsius = temperatureCelsius;
return (celsius * 9 / 5) + 32;
}
private static double FahrenheitToCelsius(double temperatureFahrenheit)
{
double fahrenheit = temperatureFahrenheit;
return (fahrenheit - 32)*5/9;
}
}
public class MyConvertViewModel
{
public double Result { get; set; }
public int Option { get; set; }
public double Input { get; set; }
public string OptionName { get; set; }
public bool ShowResult { get; set; }
}
View
#model MvcApplication1.Controllers.MyConvertViewModel
#{
ViewBag.Title = "Convert";
}
<h2>Convert</h2>
#using (Html.BeginForm("convert", "convert", FormMethod.Post))
{
<div>
Let's convert some temperatures!
</div>
<div>
#Html.LabelFor(x => x.Input, "Temp. To Convert")
#Html.TextBoxFor(x => x.Input)
</div>
<div>
#Html.LabelFor(x => x.Option, "Convert to ")
#Html.DropDownListFor(x => x.Option, new List<SelectListItem>
{
new SelectListItem {Text = "Celsius", Value = "1"},
new SelectListItem {Text = "Fahrenheit", Value = "2"}
})
</div>
<div>
<button type="submit">Convert It!</button>
</div>
}
#if (Model.ShowResult)
{
<p>#Model.OptionName : #Model.Input = #Model.Result</p>
}
disclaimer: there is a lot of shortcuts there, it is only included to give you an idea of what you should have.
So the view will post back data the user chooses, to the controller action Convert
The controller in turn will return a ViewResult object, and it will be rendered using the data captured in the view model MyConvertViewModel
Now we want to test this.
So here are some of the more important properties that it seems like you need to hook into
[TestMethod]
public void Not_A_Real_Test_But_Stuff_You_Will_Want_To_Use()
{
//arrange
var c = new ConvertController();
//act
var results = c.Convert(null) as ViewResult;
//now results is a ViewResult or null
var theViewModelProperty = results.Model as MyConvertViewModel;
var exampleResult = theViewModelProperty.Result;
var exampleInput = theViewModelProperty.Input;
//etc
//how about the view that was returned?
var theViewName = results.ViewName;
//or anything you put in the ViewData
var theViewData = results.ViewData["glosrob-example"];
Assert.Fail("This was not a real test!");
}
Hopefully this gives you an idea of how you can test for output from a controller method.
Edit: I'm not writing all your tests for you but as an e.g.
[TestMethod]
public void Convert_Should_Return_A_MyConvertViewModel()
{
//arrange
var c = new Controller();
//act
var result = c.Convert(null) as ViewResult;
//assert
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result.ViewModel, typeof(MyConvertViewModel));
}
[TestMethod]
public void Convert_Should_Return_The_Correct_View()
{
//arrange
var c = new Controller();
//act
var result = c.Convert(null) as ViewResult;
//assert
Assert.IsNotNull(result);
Assert.AreEqual("convert", result.ViewName);
}
I have register form. I want to check new username that is in db or not and if there is in DB , exception show next to it's textbox "UserName already exist...", what should I do?
this my method with exception that I have used it in Register action.:
public void InsertNewUser(MemberRegisterModel mm)
{
EShopThemeDBEntities context = new EShopThemeDBEntities(idbconnection.ConnStr);
using (context)
{
var listUsers = (from o in context.Users
select o.Username).ToList();
var a = listUsers.Count();
foreach (var item in listUsers)
{
if (mm.Username == item.ToString())
{
throw new Exception("UserName already exist...");
}
User mmr = new User();
mmr.FName = mm.FName;
mmr.LName = mm.LName;
mmr.Username = mm.Username;
mmr.Password = mm.Password;
mmr.Email = mm.Email;
mmr.Phone = mm.Phone;
mmr.Mobile = mm.Mobile;
mmr.CreateDate = DateTime.Now;
mmr.RoleId = 2;
context.AddToUsers(mmr);
context.SaveChanges();
}
}
You can set the Model error and return the model object back to view.
if(mm.Username == item.ToString())
{
ModelState.AddModelError("UserName","Username already taken";)
return View(model);
}
Also You do not need to get a list of usrs from database and do a loop to check whether the user entered user name exist or not. You can use the FirstOrDefault method to atleast one is there.
using (context)
{
var user=(from o in context.Users
where o.UserName==mm.UserName).FirstOrDefault();
if(user!=null)
{
ModelState.AddModelError("UserName","Username already taken";)
return View(model);
}
else
{
//Save new user info
}
}
Make sure you have the validation fields in your view, adjacent to the text box
#Html.TextBoxFor(m => m.UserName)
#Html.ValidationMessageFor(m => m.UserName)
But, Ideally, I would also do it asynchronosly with ajax to provide a rich user experience to the user. For that what you have to do is to look for the blur event of the text box and get the value of the textbox, make an ajax call to an action method which checks the availability of user name and return appropriate result.
<script type="text/javascript">
$(function(){
$("#UserName").blur(){
var userName=$(this).val();
$.getJSON("#Url.Action("Check","User")/"+userName,function(response){
if(response.status=="Available")
{
//It is available to register. May be show a green signal in UI
}
else
{
//not available. Show the message to user
$("#someMsgDIv").html("User name not available");
}
});
});
});
</script>
Now we should have an action method called Check in UserController to handle the ajax request
public ActionResult Check(string id)
{
bool isAvailable=false;
string userName=id;
//Check the user name is availabe here
if(isAvailable)
return Json(new { status="Available"},
JsonRequestBehaviour.AllowGet);
else
return Json(new { status="Not Available"},
JsonRequestBehaviour.AllowGet);
}
Note: Never do the client side approach only. Always do the server side checking no matter whether you have client side checking or not.
Shyju's answer is a thorough answer. However, based on your comments about handling the exception, here's a sample:
public void InsertNewUser(MemberRegisterModel mm)
{
// Some code...
if (userExists)
{
throw new ArgumentException("User name not available");
}
}
in your action method:
public ActionResult AddUser(MemberRegisterModel newUser)
{
try
{
var userManager = new MembersSrv();
userManager.InsertNewUser(newUser);
}
catch (ArgumentException ex)
{
if (ex.Message == "User name not available")
{
ModelState.AddModelError("UserName","Username already taken";)
return View(model);
}
}
}
Please note that the better way is to define a class which derives from Exception class (e.g. DuplicateUserNameException) and throw/catch that exception in your code. This sample code has been simplified.
I'm trying to bind an enum AgeRange using Html.DropDownListFor but regardless of whatever I'm choosing from View page, Controller is getting a value '0'. Can anyone help me to fix this issue?
EDIT: Controller code in place.
Enum Class:
public enum AgeRange
{
Unknown = -1,
[Description("< 3 days")]
AgeLessThan3Days = 1,
[Description("3-6 days")]
AgeBetween3And6 = 2,
[Description("6-9 days")]
AgeBetween6And9 = 3,
[Description("> 9 days")]
AgeGreaterThan9Days = 4
}
View:
#Html.DropDownListFor(
model => model.Filter.AgeRangeId,
#Html.GetEnumDescriptions(typeof(AgeRange)),
new { #class = "search-dropdown", name = "ageRangeId" }
)
Controller:
public ActionResult Search(int? ageRangeId)
{
var filter = new CaseFilter { AgeRangeId = (AgeRange)(ageRangeId ?? 0) };
}
You're close...
I'd suggest following along with this guy
You have to write an extension method for your selectlist to work.
I use this
public static SelectList ToSelectList<TEnum>(this TEnum enumeration) where TEnum : struct
{
//You can not use a type constraints on special class Enum.
if (!typeof(TEnum).IsEnum)
throw new ArgumentException("TEnum must be of type System.Enum");
var source = Enum.GetValues(typeof(TEnum));
var items = new Dictionary<object, string>();
foreach (var value in source)
{
FieldInfo field = value.GetType().GetField(value.ToString());
DisplayAttribute attrs = (DisplayAttribute)field.GetCustomAttributes(typeof(DisplayAttribute), false).First();
items.Add(value, attrs.GetName());
}
return new SelectList(items, Constants.PropertyKey, Constants.PropertyValue, enumeration);
}