Multiple views modifying one object MVC - asp.net-mvc

I am building a service which requires a somewhat lengthy setup process. I have it broken into 4 models and 4 corresponding views. They are Setup, Setup2, Setup3, and Setup4. Each of these views gathers information from the user which is stored in a User object. I have been passing the user along like this:
[HttpPost]
public ActionResult Setup(FormCollection values)
{
User registeringUser = new User();
registeringUser.email = User.Identity.Name;
registeringUser.fName = values["fName"];
registeringUser.lName = values["lName"];
registeringUser.phone = values["phone"];
return RedirectToAction("/Setup2", registeringUser);
}
For some reason, this seems to work just fine for the first jump (from Setup to Setup2) but after that I'm getting weird behavior, such as User. getting set to null when the User is passed to another View.
In a related, but slightly different issue, I need the last screen (Setup4) to be recursive. This screen adds a course in which the user is enrolled, and if they don't check the "This was my last class" button, it needs to basically clear the form so they can enter another course.
The entire Controller looks like this:
[HttpPost]
public ActionResult Setup4(FormCollection values, User registeringUser)
{
// values["allClassesAdded"] returns "false" as a string if box is unchecked, returns "true,false" if checked.
// Solution: parse string for "true"
if (utils.parseForTrue(values["allClassesAdded"]))
{
// TODO Redirect to "congratulations you're done" page.
database.CreateUserInDB(registeringUser);
return Redirect("/Home");
}
else
{
// Build course and add it to the list in the User
course c = new course(values);
if (Request.IsAuthenticated)
{
//registeringUser.currentCourses.Add(c);
registeringUser.AddCourse(c);
return RedirectToAction("/Setup4", registeringUser); // <---- This doesn't really work right
//return View();
}
else
{
return Redirect("/Account/Login");
}
}
}
This is my first project with MVC, so if you find that I'm doing the entire thing completely incorrectly, feel free to not answer the question I asked and offer the proper solution to this need. I'm moving an existing (pure) C# project to MVC and I'm mainly just stuck on how to work within MVC's interesting structure. I'm very grateful for any help you can give!
Thanks!

You can store user related data in session without passing it between requests
Smth like this
[HttpPost]
public ActionResult Step1(Step1Model model)
{
Session["UserRegistration"] = new UserRegistration
{
FirstName = model.fName,
....
}
....
}
[HttpPost]
public ActionResult Step2(Step2Model model)
{
var userRegistration = Session["UserRegistration"] as UserRegistration;
if (userRegistration == null) { return Redirrect("Step1"); }
userRegistration.SomeField = model.someField;
...
Session["UserRegistration"] = userRegistration;
....
}

Related

Have same URL for two separate pages

I am working on testing different landing pages for users, and want 50% to land on a different page. The split is being done based on odd/even IP address. However, I want the URL of the landing page to be the same, but can't figure out how to do this. So far these are the two ActionResults:
[HttpGet]
public ActionResult VerifyVoucherCode()
{
var model = new VerifyVoucherModel();
model.ActivationCode = Request.GetFirstQueryValue("value", "voucher");
return View(model);
}
[HttpGet]
public ActionResult AlternateVerifyVoucherCode()
{
var model = new VerifyVoucherModel();
model.ActivationCode = Request.GetFirstQueryValue("value", "voucher");
return View(model);
}
As you can see the code is exactly the same, which I want to avoid regardless, but they correspond with different .cshtml files and partials and display different content. I want the URL for both to be 'SignUp/VerifyVoucher' but the second one is obviously at the moment 'SignUp/AlternateVerifyVoucher'. Is there any way I can get the AlternateVerifyVoucherCode to have the same URL but display and match up with the different razor pages?
Perhaps you need one entry point with mutliple results. Maybe something like this pseudo code?
[HttpGet]
public ActionResult VerifyVoucherCode()
{
var model = new SomeModel();
if (something == verifyvoucher)
{
return View("VerifyVoucherCode", model);
}
else if (something == alternateverifyvoucher)
{
return View("AlternateVerifyVoucherCode", model);
}
else
{
//TODO: error
return View("SomeError");
}
}

MVC download of clickonce Setup.exe and display confirmation view

I'll start by saying i'm a C# MVC newbie, but I've set up a site with Identity Management and extended the database with some custom tables to store additional info about my users, so I'm not a total neophyte. I've been working on a VB WPF application that I want to deploy from my new website and that is where I'm running into an issue.
I've created a new controller (User) and a couple of views (Download) & (Setup). I created a downloadmodel used by the download view.
In abstract what I am doing is displaying the download view (get) which has three checkboxes to confirm the user has read the Overview, Installation, and Terms of Service. These are boolean values in the model. I also have a string response message in the model, that displays just above the submit button. Here is the model:
public class DownloadModel
{
public bool Overview { get; set; }
public bool Installation { get; set; }
public bool TermsOfService { get; set; }
public string Response { get; set; }
public DownloadModel()
{
Overview = false;
Installation = false;
TermsOfService = false;
Response = "After checking boxes click the button to begin installation";
}
}
My User Controller handles the Get to initially display the download view, and then in the Post it checks to see if all the checkboxes were ticked, if not it updates the response message and returns the view.
If the checkboxes are all checked then it pulls the subscriber (which must exist because it was created when the user verified their e-mail via the account controller - identity management), then proceeds to update the subscriber with the original (if new) or last download date(s). At this point I want to begin downloading the clickonce setup.exe file, before returning the setup view.
[Authorize]
public class UserController : Controller
{
// GET: User/Download
public ActionResult Download()
{
return View(new DownloadModel { });
}
// Post: User/Download
[HttpPost]
public ActionResult Download(DownloadModel downloadcheck)
{
if (!ModelState.IsValid)
{
return View(downloadcheck);
}
//check to see if all the boxes were checked
if (downloadcheck.Overview == true &
downloadcheck.Installation == true &
downloadcheck.TermsOfService == true)
{
//yes - so let's proceed
//first step is to get the subscriber
Subscriber tSubscriber = new Subscriber();
tSubscriber.Email = User.Identity.Name;
bool okLoad = tSubscriber.LoadByEmail();
if (okLoad == false)
{
//we have a real problem. a user has logged in but they are not yet
//a valid subscriber?
throw new Exception("Subscriber not found");
}
// update subscriber with download in process...
if (tSubscriber.OriginalDownload == DateTime.MinValue)
{
tSubscriber.OriginalDownload = DateTime.Now;
tSubscriber.LastDownload = tSubscriber.OriginalDownload;
}
else
{
tSubscriber.LastDownload = DateTime.Now;
}
if (tSubscriber.UpdateDownloaded() == false)
{
//update of download dates failed
//another problem that shouldnt occur.
downloadcheck.Response = "A problem occured downloading your setup."
+ "Try again. If this error continues please contact support.";
return View(downloadcheck);
}
//download dates have been updated for the subscriber so let's start the download!
//THIS IS WHERE I NEED TO BEGIN THE DOWNLOAD
return View("Setup");
}
else
{
// all boxes were not checked - update message
downloadcheck.Response = "Please confirm you have reviewed the above information "
+ "by checking all of the boxes before clicking on the button.";
return View(downloadcheck);
}
}
}
The download view is pretty straight forward, and the setup view simply confirms the download was started and provides a link to the help-setup page.
I'm really a bit lost here. I thought I'd plug in a return new filepathresponse, but I can't do that and return the setup view.
My other thought was to somehow trigger the download of my /xxx/setup.exe from within the setup view as it is returned - but I'm at a loss as to how to accomplish this.
I'll be the first to admit that my mvc c# code is probably overly verbose and my approach to how I've done this may be totally wrong, but I'm just scrambling to get this done so I can deploy my WPF app to select Beta users for testing. It's been a long time living off savings and I can smell go-live from here.
One final note, I'm using setup.exe clickonce deployment of my wpf app for simplicity, as there are .net and localsqldb prerequisites, but I will not be using automated updates - not that this is really relevant.
Appreciate all input and advice.
After more digging and hacking I've found a solution that works. Firstly in my setup view (confirmation page) I added a simple script to initiate a new function in my user controller:
<script type="text/javascript">
window.location.href = "/user/sendfile/"
</script>
The controller change was simple too. For testing I just used a txt file.
// User/SendFile
public ActionResult SendFile()
{
string path = #"~/xxxx/anyfile.txt";
string content = "application/txt";
//string content = "application/x-ms-application";
return new FilePathResult(path, content)
{
FileDownloadName = "mynewtext.txt"
};
}
What is really interesting about this solution is that the FileDownloadName is what the file content is downloaded as. So in this way I can refer to the actual setup.exe file in the path, but then name the downloaded exe anything I want. Bonus :)

Avoid to show Null or specific values to razor view engine

I am working on asp.net mvc3 web application using MS Sql server 2008 express rc2. In my app I have two different brands in DB and one of them have few Null or 'unknown' values (e.g. 'unknown' is added to DB instead of Null). My question is how to pass only non null values to View Engine instead of using If/Else statements in View?
in controller:
var model = _data.GetViewModel(query);
if (model != null)
{
return View(model);
}
else
return View("Error");
in ViewModel;
public int Id { get; set; }
public string Query { get; set; }
public string Brand { get; set; }
public string Family { get; set; }
public string Type { get; set; }
in Model:
public ViewModel GetViewModel(string query)
{
var data = _comp.Get(p => p.Query == query);
if (data == null) return null;
return new ViewModel
{
Id = data.id,
Brand = data.brand,
Family = data.family,
Type = data.type
};
}
in View (I am currently using If statement):
#if (Model.Brand != null)
{
<span class="brand">#Model.Brand</span>
}
#if (Model.Family != null)
{
<span class="family">#Model.Family</span>
}
#if (Model.Type != null)
{
<span class="type">#Model.Type</span>
}
Note: I want to avoid If statement because there are too many values in the Database of each brand, and many of the them are Null, So I don't want to generate Html for those Null values. I am using If/Else statement like above code, and for checking too many values in View using If, it costs Memory on server and processor, and it also slow down server response time.
I want to have an alternative method to do this. Should I use Partial views or anything else?
Please Please help me to solve this, Your help is very appreciated.
Thanks and Regards.
First, some background/context, then my suggestion.
(By the way, this all applies to any version of ASP.NET MVC or ASP.NET NancyFX (yes, there's another option out there!!), etc)
Context / Background
To solve this, people generally fall into two types of categories:
Just get data and let the View decide what to show (common one, not the proper way IMO).
The Controller should handle all the heavy lifting and give the view the exact answer (proper way, IMO).
The first way is quick and dirty. Sure it works, but it puts too much logic into the view. Views are not supposed to do any logic at all (exception: for loops, and maybe the odd if/else, maybe). The main reason for this is testing. Yep, that dirty word which people hate and think it's for hippies only. Or .. I don't have the time to test.. so I manually test, etc.. If you put any business logic into a view, you cannot test that.
The second way might seem a bit slower at first, but that's like anything - the more you practice, the faster you go. This is (IMO) the preferred method of doing things because you can test the controller. The controller should create a view model which will have -the exact- results that the view needs. Not extra. For example, imagine you want to return a list of Brands to the display/view. Most people do (the equivalent of) Get-all-brands into a list, and send that list to the view, even though 80% of those properties are -not- going to be used by that view! Even if ONE property is not going to be used by that view, do not retrieve it nor send it to the view!
So - TL;DR; do all the heavy lifting in the controller. The View is dumb. Just dump the exact view model data, to the view.
Solution to your problem
Ok, so let's roll with idea #2 and get all this stuff happening in the controller.
// Grab the results.
// ASSUMPTION: It is only returning the -exact- data I need. No more, no less.
var results = _data.GetViewModel(query);
if (model == null)
{
// Project the results into a perfectly tight & svelte view model
// 100% specific for this view.
var viewModel = results.
Select(x => new ViewModel
{
Id = x.Id,
Brand = string.IsNullOrEmpty(x.Brand)
? string.Empty
: x.Brand,
Family = string.IsNullOrEmpty(x.Family)
? string.Empty
: x.Family,
Type = string.IsNullOrEmpty(x.Type)
? string.Empty
: x.Type,
}).ToList();
return viewModel;
Testing this..
[Fact]
public void GivenSomeBrands_Index_ReturnsAViewModel()
{
// Arrange.
// NOTE: Our fake repostitory has some fake data. In it ..
// Id: 1, Brand: Gucci.
// Id: 22, Brand: null.
var controller = new BrandController(SomeFakeRepositoryThingy);
// Act.
var result = controller.Index(); // This calls that controller code, above.
// Assert.
Assert.IsNotNull(result); // Controller returned some result.
Assert.IsNotNull(result.Model); // We have some model data.
var model = result.Model as IList<ViewModel>(); // Cast the Model value.
Assert.NotNull(model); // We have a strongly typed view model.
// We check the first brand value.
Assert.Equal("Gucci", model.First().Brand);
// We know this item has a null Brand,
Assert.Equal(string.Empty, model[21].Brand); but the ViewModel converted it.
}
You could write a custom HTML helper:
public static string MyHelper<V>(this HtmlHelper helper, V value, string css)
{
if (value == null)
return "";
return String.Format("<span class='{0}'>{1}</span>", value, css);
}
Then in your view:
#Html.MyHelper(Model.Brand, "brand");
#Html.MyHelper(Model.Family, "family");
#Html.MyHelper(Model.Type, "type");

ASP.NET MVC: Keeping last page state

Here's the situation: i have a SearchPage where an user can make a complex search. Nothing really unusual. After the results are displayed, the user can select one of them and move to another Page (Like a Master/Detail).
I have a breacrumb which holds the places where the user has been and it can have more than 4 levels (Like Main -> 2Page -> 3Page -> 4Page -> NPage). What i want is to maintain the state of each control on my complex search page, if the user uses the breacrumb to navigate backwards, since i don't want them to manually set all those search filters again.
So far, i've been using javascript:history.back(), but since i can have multiple levels on my breadcrumb, this hasn't been very useful. I was thinking about using OutputCache to do it, but i don't know how i would proceed.
UPDATE
I've just talked to a co-worker and he told me that some of our combobox (dropdownlist) are dynamically generated. So if the user select one item on the first combobox, the second will be filled with data related to the first selection.
OutputCache would cache the results for every user. Why don't you try to store the information in a cookie with page url and filter information. Each time an action is executed, read the cookie and populate the model (custom model for search) with those values found (if they match the page url, action in this situation). Pass the model to the view and let it repopulate the search criteria text boxes and check boxes.
UPDATE:
When a user fills in the search filter text boxes, you are passing that information back to a controller somehow. Probably as some kind of a strongly typed object.
Let's say your users get to enter the following information:
- Criteria
- StartDate
- EndDate
There is a model called SearchCriteria defined as:
public class SearchCriteria
{
public string Criteria { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
}
Your action could look something like this:
[HttpGet]
public ViewResult Search()
{
SearchCriteria criteria = new SearchCriteria();
if (Request.Cookies["SearchCriteria"] != null)
{
HttpCookie cookie = Request.Cookies["SearchCriteria"];
criteria.Criteria = cookie.Values["Criteria"];
criteria.StartDate = cookie.Values["StartDate"] ?? null;
criteria.EndDate = cookie.Values["EndDate"] ?? null;
}
return View(criteria);
}
[HttpPost]
public ActionResult Search(SearchCriteria criteria)
{
// At this point save the data into cookie
HttpCookie cookie;
if (Request.Cookies["SearchCriteria"] != null)
{
cookie = Request.Cookies["SearchCriteria"];
cookie.Values.Clear();
}
else
{
cookie = new HttpCookie("SearchCriteria");
}
cookie.Values.Add("Criteria", criteria.Criteria);
if (criteria.StartDate.HasValue)
{
cookie.Values.Add("StartDate", criteria.StartDate.Value.ToString("yyyy-mm-dd"));
}
if (criteria.EndDate.HasValue)
{
cookie.Values.Add("EndDate", criteria.EndDate.Value.ToString("yyyy-mm-dd"));
}
// Do something with the criteria that user posted
return View();
}
This is some kind of a solution. Please understand that I did not test this and I wrote it from top of my head. It is meant to give you an idea just how you might solve this problem. You should probably also add Action to SearchCriteria so that you can check whether this is an appropriate action where you would read the cookie. Also, reading and writing a cookie should be moved into a separate method so that you can read it from other actions.
Hope this helps,
Huske

MVC 3 - Widget fine in Create action, but has null values in View action

I'm using ASP.NET MVC3, with Entity Framework.
I have a Widget controller, with standard Widget CRUD actions.
In my Create action, I successfully create a new Widget object, which has two FooBar objects. This is added to my database just fine, and the action the redirects to the View action.
[HttpPost]
public ActionResult Create(Widget model)
{
if (ModelState.IsValid)
{
//At this point, the widget has two FooBar properties. I can see the values for these FooBars just fine.
if (repo.AddWidget(model))
{
ViewBag.Message = "Your widget has been created.");
return RedirectToAction("View", new { id = model.Id });
}
else
{
ViewBag.Error = "Woops, something went wrong. Please try again.");
}
}
return View(model);
}
In the View action, I fetch the newly created Widget from my repository - except now the two FooBar properties are null.
public ActionResult View(int id)
{
var widget = repo.GetWidget(id);
if (widget == null)
{
ViewBag.Error = "No widget found for the specified ID";
return RedirectToAction("Find");
}
//At this point, the widget has two null values for the FooBar1 and FooBar 2 properties
return View(widget);
}
In the database itself I can see the correct FooBar ID values on my Widget.
My model is set up pretty much exactly the same as shown in this tutorial:
http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx
public class WidgetContext : DbContext
{
public DbSet<Widget> Widgets { get; set; }
public DbSet<FooBar> FooBars { get; set; }
}
Can anyone suggest how I might start tracking this issue down?
Update:
I should clarify the values are null whenever I call the View action, not only after a Create.
Looks like FooBar is separate entity and FooBar1 and FooBar2 are navigation properties. In such case you must either explicitly say you want to loade them (we call this eager loading):
var widget = context.Widgets
.Include(w => w.FooBar1)
.Include(w => w.FooBar2)
.SingleOfDefault(w => w.Id == id);
Note: Strongly typed Include requires EF 4.1 for EFv1 or EFv4 use:
var widget = context.Widgets
.Include("FooBar1")
.Include("FooBar2")
.SingleOfDefault(w => w.Id == id);
or create custom strongly typed extension method like this.
or you must turn lazy loading on. Lazy loading makes separate queries to database once properties are first accessed in your view. It requires making both FooBar1 and FooBar2 virtual and context must be alive when view is rendered. Usually this is handled by singe context per HTTP request where context is for example created and disposed in custom controller factory or in custom Http module.
Also next time make your question complete please. You have shown a lot of code but the important parts (Windget class and GetById method) are missing. Unfortuanatelly users here aren't oracles so we need to now necessary details. Both action methods are almost irrelevant to your problem.

Resources